Compare commits

...

2980 Commits

Author SHA1 Message Date
Alex Carver [Acerock7] 103f60a4ff
Bug 41253: Add new permission to superlibrarian test
Adds items_modification_by_age to the superlibrarian subtest in t/Koha/Auth/Permissions.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-10-01 15:04:44 +00:00
Tomás Cohen Arazi d1f8d5bbd2
Bug 42719: (QA follow-up) Adjust tests
This patch makes the tests reflect the design decision made on the bug report.
The author for the original 'No CGISESSID then error' patch signed off on this
so I'm confident this is the right thing to do.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-10-01 13:32:51 +00:00
Lucas Gass 9f50aa9862
Bug 42667: (follow-up) Fix POD coverage
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-10-01 13:32:50 +00:00
Jonathan Druart 541cbd84a5
Bug 42685: Fix Cypress test
AssertionError: Timed out retrying after 10000ms: Expected to find content: 'Requesting Agencies' within the element: <div.sidebar_menu> but never did.
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-30 09:14:54 +00:00
Jonathan Druart 36606ea9d6
Bug 41674: Fix review issues - remove unused import, fix POD synopsis, remove unused variable
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-30 09:14:52 +00:00
Jonathan Druart 8526f2ac3b
Bug 41674: Use LinkPref for CurbsidePickup
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-30 09:14:51 +00:00
Jonathan Druart a746db644c
Bug 41674: Add a test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-30 09:14:50 +00:00
Owen Leonard e7aee90141
Bug 41674: (follow-up) Proof of concept: branches.pl
This patch implements the new template plugin on Administration ->
Libraries.

- When viewed as a user with permission to manage system preferences,
  the system preference names under "Reply-To", "Return-Path", and "MARC
  organization code" should be linked to system preferences, and the
  link should take you to the correct preference search.
- When viewed as a user without permission the system preference names
  should not be linked.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-30 09:14:49 +00:00
Owen Leonard 797c1b352e
Bug 41674: Add template plugin for linking to system preferences based on user permission
This patch adds a new template plugin, LinkPref, for displaying system
preference names as links depending on the logged-in user's permission.

Syntax:

  [% "SYSTEM_PREFERENCE_NAME" | html | $LinkPref %]

For a user with 'CAN_user_parameters_manage_sysprefs' permission it
outputs:

  <a class="link_preference" href="admin/preferences.pl?op=search&amp;ok=Search&amp;searchfield=SYSTEM_PREFERENCE_NAME">SYSTEM_PREFERENCE_NAME</a>

For a user without permission it outputs:

  <span class="link_preference">SYSTEM_PREFERENCE_NAME</span>

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-30 09:14:45 +00:00
Martin Renvoize 106fabbf9a
Bug 43669: Make t::lib::Selenium wait for the server to be ready
This patch adds a wait_for_server_ready check to t::lib::Selenium, so
Selenium-based tests poll the server until it responds successfully
before driving it, instead of assuming it is already up.

This guards against a race we saw on Jenkins CI:
t/db_dependent/selenium/00-onboarding.t and
t/db_dependent/selenium/01-installation.t both failed with 'no such
element: //div[@class="alert alert-success"]' right after the DB
connection check step, immediately after run_tests.pl's
get_commands_to_reset_db() restarted Apache/Plack. That helper
restarts services and moves straight to running the tests with no
readiness wait, so the very first requests can hit workers that are
still warming up. The test then fails on a missing page element even
though there is no bug in the installer flow itself.

A companion issue has been filed against koha-misc4dev to add a
readiness wait to run_tests.pl itself:
https://gitlab.com/koha-community/koha-misc4dev/-/work_items/108

Test plan:
1. Apply the patch
2. Run t/db_dependent/selenium/01-installation.t (or 00-onboarding.t)
   against a fresh empty database with KOHA_TESTING=1, as usual
3. Confirm it still passes normally
4. To exercise the new wait: drop/recreate the database, then run
   "sudo service apache2 restart" and "sudo service koha-common
   restart", then immediately run the selenium test with no delay.
   Confirm it either passes (server came up in time) or fails fast
   with a clear "Cannot wait more for the server to be ready" message
   rather than a confusing missing-element error deep in the install
   wizard

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 16:03:56 +00:00
nela a86b7a063d
Bug 43650: incremental EAN-13 barcodes produce unexpected sequence
When incremental EAN-13 barcodes are selected for autoBarcode and new item
records are created an unexpected sequence of barcode numbers is produced
instead of a sequence where the barcode is incremented by 1 between each item.
This patch fixes that by adding quote marks in the inline JS, thus treating the
barcode as a string rather than an integer :+)

Test plan:
1. have records imported from somewhere but with no items
2. autoBarcode set to incremental EAN-13 barcodes
3. create a number of new items
4. observe that the item barcodes don't follow the expected sequence
5. apply patch
6. delete item records
7. repeat step 3
8. observe that the item barcodes follow the expected sequence

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 16:03:52 +00:00
Martin Renvoize e1e08a7f5d
Bug 40901: (QA follow-up) Keep daemon output in the traditional log files under systemd
Under koha-sysv the daemon(1) wrapper captures each daemon's stdout and
stderr to /var/log/koha/INSTANCE/NAME-output.log and NAME-error.log.
The systemd units sent that output to the journal instead, so
zebra-*.log, indexer-*.log, es-indexer-*.log, worker-*.log and
sip-error.log silently stopped being written on migrated hosts, and
log locations differed between the two init packages.

Use StandardOutput=append: and StandardError=append: on those six
units so the files are identical whichever package supervises the
daemons. The shipped logrotate stanza uses copytruncate, which is safe
with append-mode descriptors. The journal still records systemd's own
messages about each unit, which is what matters when one fails to
start, and README.Debian shows the drop-in for sites that prefer the
journal. Plack and the Z39.50 responder already wrote their own files.

The README, NEWS.Debian entry and koha-common(8) text that described
the journal-only behaviour are updated accordingly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:39 +00:00
Martin Renvoize 0aa04d9ddc
Bug 40901: (QA follow-up) Document the two init packages, the new options and the logging change
- koha-systemd.README.Debian was written for the first prototype: it
  named koha-worker-long@.service (now koha-worker-long_tasks@), said
  SIP needs the sip.enabled flag file (no longer read on systemd hosts),
  described coexisting with koha-common and a manual migration that the
  postinst now performs. Rewrite it to describe the actual units, how
  the koha-* helpers map onto them, where each configuration value is
  read from, how to relax the sandboxing with a drop-in, and the
  migration.
- Logging is the one visible behavioural change and was undocumented:
  with koha-sysv the daemon(1) wrapper captured stdout/stderr to
  /var/log/koha/INSTANCE/NAME-output.log and NAME-error.log; with
  koha-systemd that output is in the journal, so zebra-*, indexer-*,
  es-indexer-*, worker-* and sip-error.log are no longer written, while
  everything Koha writes itself (Starman's plack.log/plack-error.log,
  the log4perl files, z3950.log, the Apache vhost logs) is unchanged.
  Spell that out in README.Debian, in a new koha-systemd NEWS.Debian
  entry shown by apt-listchanges on upgrade, and in a "Service
  management" section of the koha-common(8) man page.
- Add --enable/--disable to the koha-zebra, koha-worker, koha-indexer
  and koha-es-indexer man pages.
- z3950_responder.pl's synopsis listed --config-dir twice and had lost
  -c, which is still a YAZ pass-through option; put -c back.
- t/00-valid-systemd-units.t: give it the usual shebang, and ignore
  every "Command ... is not executable" line from systemd-analyze rather
  than only the ones for scripts in this repository. Whether starman or
  /usr/share/koha/bin/... is installed on the host running the test says
  nothing about the unit files, and the old filter made the test fail on
  any development machine without the Koha packages.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:38 +00:00
Martin Renvoize ffe8b55b57
Bug 40901: (QA follow-up) Make koha-disable/koha-enable symmetric and fix koha-remove/koha-create
koha-disable had gained "koha-sip --disable" and an unconditional
"koha-plack --disable". On both backends those are not just systemd
bookkeeping: on SysV the former deletes the sip.enabled flag and the
latter comments the Plack includes out of the Apache vhost (which
koha-disable then restarts Apache to apply). koha-enable was unchanged,
so "koha-disable x && koha-enable x" left the instance on CGI with SIP
off. Go back to stopping the daemons only (the new Plack and ES indexer
stops are kept), and under systemd stop and disable the per-instance
koha@<inst>.target so the instance stays down across reboots while the
individual units keep their enablement. koha-enable re-enables and
starts that target, so an enabled instance comes back exactly as it
was.

koha-remove nested every --disable inside "if is_*_running", so a unit
that was enabled but stopped kept its symlink in
koha@<inst>.target.wants/ after the instance was gone, which is the
very thing the block below it says it prevents. Disable unconditionally.

koha-create aborted after the database and vhost existed if a
systemd-only --enable failed, because the helpers run under set -e;
guard them like the systemctl calls already were. It also only started
koha.target, which is a no-op when the target is already active, so
the new instance's units were not started; start koha@<name>.target
itself.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:36 +00:00
Martin Renvoize 0bdb94bfbb
Bug 40901: (QA follow-up) Harden the service abstraction in koha-functions.sh
- koha_init_backend relied on "systemctl list-unit-files" exiting
  non-zero when nothing matches. That behaviour is version dependent,
  and on a systemd host running koha-sysv a false positive would send
  every koha-* helper to units that do not exist. Test for the
  koha-plack@.service unit file instead: unambiguous, and no systemctl
  fork per call. The _KOHA_INIT_BACKEND cache is dropped; every caller
  ran the function in a $(...) subshell so it never took effect anyway.
- The _sysv_worker and _sysv_z3950 code moved here from bash-only
  scripts brought "((error_count++))" and "[[ ! $VAR ]]" into a
  #!/bin/sh library that koha-disable, koha-enable, koha-remove,
  koha-list and the init script source under dash. Use POSIX
  equivalents.
- _sysv_restart_zebra and _sysv_restart_sip lost the explicit
  "return 0" of the original quiet not-running branch, so a start
  failure now aborted the caller's whole instance loop under set -e
  (including the init script's restart across all instances). Restore
  it.
- systemctl status is run with --no-pager from koha_service_ctl and
  koha-systemd-ctl so the helpers never block on a pager.
- koha-plack --enable/--disable ran systemctl before editing the Apache
  configuration, so a systemctl failure under set -e left the vhost
  untouched. Do the systemd half after the Apache edits and warn
  instead of aborting.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:35 +00:00
Martin Renvoize 606fc6bcd6
Bug 40901: (QA follow-up) Fix the koha-systemd migration, upgrade and removal paths
First install (migration from the SysV layout):

- The postinst ran daemon-reload before stopping koha-common.service, so
  the name already resolved to the new native unit and the stop was a
  no-op; the daemon(1)-supervised starman/zebrasrv/workers survived and
  kept plack.sock and the Zebra sockets. The init script itself is gone
  by then (koha-common's rm_conffile) and the koha-* helpers already
  dispatch to systemd, so neither can be used either. Kill the legacy
  koha-common.service cgroup (the generated unit has KillMode=process,
  a plain stop leaves the supervisors alive), sweep remaining pid files,
  and wait for them to exit, all before daemon-reload.
- Nothing ever started the new units, and koha-common.service was never
  enabled, so a migrated host came back with every instance stopped and
  "systemctl restart koha-common" doing nothing. Enable both koha.target
  and koha-common.service and start them through deb-systemd-invoke.
- koha-plack@ was enabled for every instance including CGI-only ones,
  and koha-indexer@ regardless of USE_INDEXER_DAEMON. Use
  "koha-list --enabled --plack" and /etc/default/koha-common like the
  init script did.
- Drop the "systemctl disable koha-common.service" that targeted our
  own compat unit.

Upgrade: deb-systemd-invoke takes unit names, not glob patterns, so
enumerate the active koha-*@*.service units before try-restarting them.

Removal: the prerm only did a daemon-reload, leaving every daemon
running with its unit file deleted, so swapping to koha-sysv started a
second copy of each. Stop koha-common.service and koha.target on
remove; a new postrm removes the enablement symlinks on purge and
reloads the manager.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:34 +00:00
Martin Renvoize 1a81cf9bc7
Bug 40901: (QA follow-up) Polish the per-component units
- Order services Before= their instance target instead of After=. With
  After=, koha@<inst>.target (and therefore koha.target) reported active
  before a single daemon had started, so its state meant nothing to
  anything ordered after Koha. koha@.target is likewise Before=koha.target.
- Add SyslogIdentifier=%i-koha-<service> to every unit so journal lines
  can be filtered per instance and component (the template this series
  removed already did this) and Documentation= pointing at each helper's
  man page.
- koha-plack@: stop with SIGQUIT and a 30s timeout. Starman treats QUIT
  as graceful shutdown and TERM as immediate; the SysV path used
  QUIT/30/KILL/5, so the default SIGTERM cut in-flight requests on every
  restart. Drop the redundant --user/--group starman flags, User=/Group=
  already apply.
- koha-worker@, koha-worker-long_tasks@: KillMode=mixed with a 120s
  timeout so the parent is signalled but a running forked job gets a
  chance to finish before it is killed.
- koha-zebra@: remove every *socket file in the run directory rather
  than the two default names, so sites with customised <listen> entries
  get the same stale-socket protection.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:32 +00:00
Martin Renvoize 5562b26bb0
Bug 40901: (QA follow-up) Fix Z39.50 option expansion and honour the indexer defaults
Two problems in how the units consume koha-generate-env's output:

1. koha-z3950@.service passed ${Z3950_ADDITIONAL_OPTS}. In systemd unit
   files "${VAR}" always expands to exactly one argument, and to a single
   empty argument when the variable is empty; only unbraced "$VAR" is
   word-split. So a configured value like "--add-item-status k -t 5"
   reached z3950_responder.pl as one unknown option, and the default
   empty value reached it as a "" listener address. Verified with
   systemd-run: ${X}="--a --b" gives ["--a --b"], ${Y}="" gives [""],
   $X gives ["--a"] ["--b"]. Use the unbraced form.

2. koha-indexer@.service hardcoded rebuild_zebra.pl -daemon -sleep,
   ignoring ALTERNATE_INDEXER_DAEMON and INDEXER_PARAMS from
   /etc/default/koha-common that the SysV path honours. koha-generate-env
   now resolves INDEXER_DAEMON and INDEXER_PARAMS exactly like
   koha-indexer and the unit runs them through /usr/bin/env, so any
   configured indexer works.

koha-generate-env is also tightened up: numeric values are validated
and fall back to their defaults, every value is forced onto one line
without quote characters so the EnvironmentFile always parses, the
debug_mode test matches is_debug_mode, Z3950_ADDITIONAL_OPTS is read
from /etc/default/koha-common first (as before this patchset), the
Z39.50 config dir uses the same config.xml test as is_z3950_enabled,
the env dir is created with the instance's ownership if it does not
exist yet, and unknown services get an empty env file instead of a
chmod failure. The responder is invoked through /usr/bin/perl like the
other Perl daemons.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:31 +00:00
Martin Renvoize 3fb9421561
Bug 40901: (QA follow-up) Make koha-common.service a dependency-free shim
The compat unit declared BindsTo=koha.target and After=koha.target while
its ExecStop ran "systemctl stop koha.target". After= orders this unit's
stop before the target's, so the ExecStop waited on a job that could not
run until the ExecStop itself finished. Every "systemctl stop koha-common"
and every "systemctl restart koha-common" hung for TimeoutStopSec (90s
by default) and left the unit in failed state. An async --no-block stop
does not help either: the queued target stop replaces the start job the
restart transaction needs, and everything ends up stopped.

Drop the dependencies. The unit is now a plain oneshot with
RemainAfterExit that starts koha.target in ExecStart and stops it in
ExecStop, which is all the old "service koha-common" UX requires.
Verified with a replica set of user units: start, stop and restart of
the shim each complete in well under a second, restart gives every
service a new PID, and managing koha.target directly is unaffected.
The one cosmetic consequence is that the shim stays "active (exited)"
if koha.target is stopped behind its back; README.Debian says to look
at koha.target for real status.

Also sets SyslogIdentifier so its journal lines are identifiable.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:30 +00:00
Martin Renvoize 529df17b42
Bug 40901: (QA follow-up) Install units to /lib/systemd/system and tidy control
The old koha-common package shipped /lib/systemd/system/koha-common.service
while koha-systemd installs to /usr/lib/systemd/system. On a merged-/usr
Debian 12 host these are the same file through the /lib symlink, but
dpkg compares path strings, so Replaces does not apply and removing the
old package's copy can delete the file koha-systemd just unpacked
(the DEP-17 aliasing problem). Keep the units under lib/systemd/system,
the path the previous package used, so this is an ordinary takeover.

While here:
- koha-core gains Recommends: koha-systemd | koha-sysv. Installing it
  alone previously gave helpers that fell back to the daemon(1) wrapper
  that no longer gets installed.
- drop a stray comment from the Source stanza of control.in
- stop appending Perl dependencies to koha-common.substvars: the
  metapackage no longer uses ${koha:Depends}

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:28 +00:00
Martin Renvoize 2992ea058b
Bug 40901: (QA follow-up) Migrate debconf answers from koha-common to koha-core
The debconf questions moved from the koha-common/* to the koha-core/*
namespace, but nothing carried the existing answers across, so a site
that had opted out of automatic translation updates was asked again
and defaulted back to "yes". koha-core.config now seeds the new
question, including its "seen" flag, from the old one when it exists.

debian/koha-common.templates was left behind after koha-common.config
was deleted; the metapackage registered three questions that no config
script ever asks, so remove it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:26 +00:00
Martin Renvoize 67d9b903d6
Bug 40901: (QA follow-up) Restore the RabbitMQ and memcached steps in koha-common.postinst
The monolithic koha-common.postinst ended by enabling the rabbitmq_stomp
plugin, restarting RabbitMQ and restarting memcached (Bug 35242). None
of that survived the split: koha-core.postinst never had it and the new
koha-common.postinst was an empty stub. A fresh "apt install koha-common"
therefore left the STOMP plugin disabled, and upgrades stopped flushing
memcached, which is exactly the stale-cache class of bug 35242 fixed.

koha-common is the package that depends on rabbitmq-server and
memcached, so the steps belong in its postinst. The copy in
koha-full.postinst is dropped now that koha-full depends on
koha-common.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:25 +00:00
Martin Renvoize 9485938333
Bug 40901: (QA follow-up) Keep /etc conffiles under their koha-common names
The cron, logrotate and defaults files were renamed from koha-common.*
to koha-core.*, so debhelper installed them as /etc/cron.d/koha-core,
/etc/logrotate.d/koha-core and /etc/default/koha-core. dpkg never
deletes a conffile that a package stops shipping, so after an upgrade
both the old and the new files exist side by side:

- every cron job (process_message_queue.pl every 15 minutes,
  overdue_notices.pl, fines.pl, backups...) runs twice
- logrotate complains about duplicate log entries every night
- the admin's /etc/default/koha-common (USE_INDEXER_DAEMON,
  INDEXER_PARAMS, ES_* tuning) is replaced by a symlink to a fresh
  copy of the packaged defaults, silently losing every customisation

Keep the on-disk names instead: koha-core now ships the files as
debian/koha-core.koha-common.* and debian/rules passes
--name=koha-common to dh_installinit, dh_installcron and
dh_installlogrotate. Because koha-core Replaces the old koha-common,
dpkg hands each conffile over with the admin's content intact and no
duplicates are left behind. The defaults file content is byte-identical
to the old koha-common.default so nobody gets a spurious conffile
prompt, and koha-generate-env reads the single canonical name again.

The experimental pre-split koha-core (used by koha-full) had its own
/etc/default/koha-core and /etc/init.d/koha-core; a new
koha-core.maintscript migrates the former with mv_conffile and removes
the latter, with the matching rc.d links dropped in the postinst.
/etc/koha/koha-worker@.service, whose template was deleted, is removed
from upgrading systems too. koha-core.preinst gains the #DEBHELPER#
token the maintscript machinery needs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:24 +00:00
Martin Renvoize 760641c550
Bug 40901: (QA follow-up) Install the SysV init script through dh_installinit
debhelper treats debian/<package>.init as that package's init script, so
koha-sysv would have shipped /etc/init.d/koha-sysv (with generated
update-rc.d and invoke-rc.d handling) as well as the .install-copied
/etc/init.d/koha-common (with none). That meant two identical init
scripts, two generated units on systemd hosts, and on a fresh
non-systemd install the "update-rc.d koha-common disable" call in the
postinst aborting with "no runlevel symlinks to modify" because
nothing had ever created them.

Rename the script to debian/koha-sysv.koha-common.init and run
dh_installinit --name=koha-common for koha-sysv. debhelper now installs
it as /etc/init.d/koha-common, registers the rc.d links, starts it on
install, restarts it on upgrade, stops it on removal and removes the
links on purge, which is exactly what the old koha-common package did.
The hand-written prerm/postrm and the Bug 18250 disable/enable dance
(a one-off link reshuffle from 2017 that is a no-op under
dependency-based boot) are dropped in favour of the generated code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:22 +00:00
Martin Renvoize b8b064bf40
Bug 40901: (QA follow-up) Fix Replaces/Breaks version bound for the package split
The split lands in the 26.06 development cycle (Koha.pm is at
26.06.00.xxx), but the Replaces/Breaks on the old monolithic
koha-common were written as (<< 25.11). Released 25.11.x and 26.05.x
koha-common packages therefore don't match, and upgrading from them
aborts with dpkg's "trying to overwrite '/usr/sbin/koha-list', which
is also in package koha-common".

Use (<< 26.06~) everywhere, including the rm_conffile version in
koha-common.maintscript, so every pre-split release is covered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:21 +00:00
Kyle M Hall 699a7a3c0c
Bug 40901: (QA follow-up) Use /run instead of legacy /var/run in unit files
systemd 257 warns that these units point PIDFile at the legacy
/var/run directory. This patch updates the remaining /var/run/koha
paths to /run/koha to match the EnvironmentFile lines; /var/run is a
symlink to /run on Debian, so nothing changes at runtime.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:19 +00:00
Kyle M Hall 29e31a2257
Bug 40901: (QA follow-up) Make the systemd unit validation test run in KTD
The test failed in KTD instead of skipping, and the systemd-analyze
option it relied on doesn't exist in any systemd QA will meet, so the
unit files were never actually checked anywhere. Load Test::NoWarnings
after the plan, use the SYSTEMD_UNIT_PATH environment variable
instead, and ignore noise from helper scripts the packages haven't
installed yet.

Test Plan:
1) Apply this patch
2) prove t/00-valid-systemd-units.t
3) Note all units are now verified instead of the file failing!

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:18 +00:00
Kyle M Hall 7169ea9e2d
Bug 40901: (QA follow-up) Move ES indexer re-discovery into koha-systemd-ctl
koha-common.service had an unexplained one-line shell loop in
ExecStartPre. It's now a documented sync-es-indexers command in
koha-systemd-ctl that does the same thing: notice instances that
switched to Elasticsearch and start their indexers, like the SysV init
script did on every start.

https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40901#c132
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:17 +00:00
Kyle M Hall a46f1aa4ca
Bug 40901: (QA follow-up) Don't stop all Koha services on every koha-systemd upgrade
The postinst stopped and disabled koha-common.service on every
configure, which after migration means every upgrade of this package
shuts down every Koha instance. Now it only happens on first install,
where it exists to stop the old SysV-started services. The hand-rolled
deletion of the old init script is replaced with dpkg's standard
rm_conffile mechanism.

https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40901#c130
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:15 +00:00
Kyle M Hall 67ec61a60f
Bug 40901: (QA follow-up) Add Replaces/Breaks on old koha-common to koha-systemd
The old koha-common package shipped the same koha-common.service file
that koha-systemd now ships. Declaring Replaces/Breaks lets upgrades
hand the file over cleanly, matching what koha-sysv and koha-core
already declare.

https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40901#c129
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:14 +00:00
Kyle M Hall de946eac7f
Bug 40901: (QA follow-up) Remove redundant unit files ( koha-sysv.service, koha-worker@.service )
Nothing ever used either of these files. On systemd hosts koha-sysv
already gets a generated koha-common.service, so shipping its own unit
meant two units driving the same init script. The koha-worker@.service
sample in /etc/koha was a copy-by-hand template that koha-systemd's
real worker unit replaces.

https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40901#c129
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:13 +00:00
Kyle M Hall 653d6092b5
Bug 40901: (QA follow-up) Make koha-full depend on koha-common so it gets an init system
After the package split, installing koha-full got you Koha with no way
to start its services. Depending on koha-common brings in an init
system and the other services, leaving only the database server to
add.

https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40901#c128
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:11 +00:00
Martin Renvoize 71f9a68679
Bug 40901: (QA follow-up) Fix systemd unit test for Test::NoWarnings and older systemd
Add Test::NoWarnings and probe for --unit-path support, skipping on
systemd versions that don't recognise the flag (e.g. KTD's Debian image).

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:10 +00:00
Martin Renvoize 445b955b78
Bug 40901: (QA follow-up) Fix koha-disable --disable idempotency under set -e
koha-plack, koha-zebra, koha-indexer and koha-es-indexer all return
exit code 1 when asked to disable a service that is already disabled.
koha-disable has set -e, so this propagated as a fatal error when
koha-create called koha-disable on a freshly-created instance (where
none of the per-service configs are enabled yet).

Apply the same || true guard that koha-worker already had to the four
unconditional --disable calls. The desired postcondition (service
disabled) is achieved either way; the exit code from the sub-script
is not meaningful here.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:08 +00:00
Martin Renvoize 6b54ee976b
Bug 40901: Add systemd unit file validation test
Adds t/00-valid-systemd-units.t which runs systemd-analyze verify
against every unit file in debian/systemd/. Skips gracefully if
systemd-analyze is not installed (requires the systemd package).

To enable this test in KTD-based CI, add 'systemd' to the KTD
container's package list. systemd-analyze verify does not require
systemd to be running as PID 1 — it is pure static analysis and
works correctly in a standard Docker container.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:07 +00:00
Martin Renvoize 3eb65c1041
Bug 40901: (QA follow-up) Drop redundant ExecStart systemctl call in koha-common.service
koha-common.service already declares BindsTo=koha.target and
After=koha.target.  BindsTo= implies Requires=, so systemd will start
koha.target automatically when this compat unit starts and will wait for
it to be active before proceeding.  The explicit

    ExecStart=/bin/systemctl start koha.target

was therefore redundant, and calling systemctl from within a service body
is an anti-pattern (it makes an extra D-Bus round-trip back to PID 1 and
can deadlock in edge cases during early boot).

Replace with /bin/true so the oneshot succeeds immediately after the
ExecStartPre ES-indexer discovery step.  ExecStop is kept as-is because
there is no declarative equivalent for "stopping this unit should stop
koha.target" — BindsTo= only propagates stops in the other direction.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:05 +00:00
Martin Renvoize 868947cb55
Bug 40901: (QA follow-up) Quote path/option values in koha-generate-env env files
systemd EnvironmentFile parsing treats unquoted values as ending at the
first whitespace.  PLACK_PSGIFILE and Z3950_CONFIGDIR are paths that
could in principle contain spaces; Z3950_ADDITIONAL_OPTS routinely holds
multiple flags separated by spaces (e.g. "--port 9999 --verbose").

Wrap these three values in double-quotes in the generated env file so
systemd reads the full value even when it contains spaces.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:04 +00:00
Martin Renvoize 6d3b026d91
Bug 40901: (QA follow-up) Fix koha-sysv.postinst configure guard and double-enable
Two problems in the original koha-sysv.postinst:

1. #DEBHELPER# was placed at the top (line 5) before any case statement,
   so whatever dh_installsystemd expands it to ran for ALL postinst
   invocations (abort-upgrade, abort-remove, triggered…), not just
   configure.

2. The custom block manually called `deb-systemd-helper enable
   koha-common.service` on systemd hosts.  Because koha-sysv also ships
   koha-sysv.service (which dh_installsystemd enables via #DEBHELPER#),
   both koha-sysv.service and the sysv-generator-created koha-common.service
   could end up enabled, resulting in a double-start of the init script
   at boot.

Fix both by wrapping the custom code in `case "$1" in configure)`, moving
manual enable: dh_installsystemd already enables and starts
koha-sysv.service correctly.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:03 +00:00
Martin Renvoize 53ebfcb8c5
Bug 40901: (QA follow-up) koha-disable must disable units even when stopped
On systemd, koha-disable gated both --stop and --disable behind
is_*_running checks (systemctl is-active).  A service that was enabled
but not currently active (e.g. stopped for maintenance) was therefore
never disabled, leaving its unit enabled and able to restart on the next
boot — so the instance was never truly disabled.

Separate the concerns: keep the is_*_running guard around --stop (so we
don't try to stop an already-stopped service), but call --disable
unconditionally for plack, zebra, indexer, and es-indexer.  The disable
call is idempotent on both systemd and SysV, so calling it on an already-
disabled unit is safe.

SIP was already handled correctly (using is_sip_enabled).  Worker units
already used || true so were unaffected.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:01 +00:00
Martin Renvoize 3644a8407f
Bug 40901: (QA follow-up) Fix stale /etc/default/koha-common reference in koha-generate-env
koha-generate-env sourced /etc/default/koha-common to read
INDEXER_TIMEOUT, but the defaults file was renamed to
/etc/default/koha-core in this patchset.  On fresh installations no
/etc/default/koha-common exists, so INDEXER_TIMEOUT was always silently
fixed at the built-in default of 5, ignoring any admin customisation.

Read /etc/default/koha-core first; fall back to /etc/default/koha-common
so that systems upgrading from the pre-split packaging still pick up
their existing settings.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:02:00 +00:00
Kyle M Hall b5000b8869
Bug 40901: (follow-up) Systemd hardening
I think the following systemd security measures are safe for Koha:

ProtectSystem=full
Sets /usr & /boot to read-only

ProtectHome=yes
Hides /home, /root and /run/user
NOTE: The hiding of /run/user through me off, but google says it's only for
interactive logins only. AFAIK the only thing that does that is
koha-shell which isn't a systemd thing so it is unaffected

PrivateTmp=yes
Private /tmp & /var/tmp
NOTE: AFACT nothing in Koha shares files via /tmp which feels like it
would be a bad thing to do anyway

NoNewPrivileges=yes
setuid binaries can't re-elevate
NOTE: This feels like the most important one!

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:59 +00:00
Kyle M Hall 05228589ed
Bug 40901: (follow-up) Drop redundant StopWhenUnneeded on koha@.target
PartOf=koha.target already cascades stop to the per-instance target
so StopWhenUnneeded is not needed and adds more complication that could
trip us up later.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:58 +00:00
Kyle M Hall 88926c5032
Bug 40901: (follow-up) Tighten RestartSec on SIP, Z3950, es-indexer
30 seconds between restart attempts is a very long time especially for
AIP. With StartLimitIntervalSec at 60, and RestartSec at 5 we get 6
retries and what is hopefully a less visible downtime window for
the tcp services. For es-indexer it's not so big a deal but could be
an issue for cataloguers.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:57 +00:00
Kyle M Hall a84dbc3d29
Bug 40901: (follow-up) Order per-component units after koha-create-dirs
Pair each Requires=koha-create-dirs@%i.service with a matching
After=, and drop the now redundant Before= list from
koha-create-dirs@.service.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:56 +00:00
Kyle M Hall a6226588f6
Bug 40901: (follow-up) Cap restart attempts on per-component units
Add StartLimitIntervalSec=60 add StartLimitBurst=3 so a broken-at-
startup daemon stops looping at after three attempts.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:55 +00:00
Kyle M Hall 0f2f88371d
Bug 40901: (follow-up) Re-discover ES indexers on koha-common start
The SysV init script runs `koha-list --enabled --elasticsearch` at
every start. ExecStartPre on koha-common.service restores that.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:53 +00:00
Kyle M Hall 1425743c10
Bug 40901: (follow-up) koha-remove leaves koha@<inst>.target enabled
Disable koha@<inst>.target and koha-create-dirs@<inst>.service so
their koha.target.wants/ symlinks don't outlive the instance.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:52 +00:00
Kyle M Hall d0d193ea7e
Bug 40901: (follow-up) Clean up stale Zebra Unix sockets on startup
zebrasrv removes its Unix sockets on clean exit but leaves them behind on
SIGKILL, OOM, or sysv to systemd swap, causing 'Address already in use' on
the next start. ExecStartPre rm makes the unit auto-fix itself.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:51 +00:00
Kyle M Hall 5e37c3e412
Bug 40901: (follow-up) Allow seamless swap between koha-systemd and koha-sysv
koha-systemd and koha-sysv have always declared mutual Conflicts
so you can't install both, but they don't declare Replaces which
would make it so installing one autmatically removes the other

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:50 +00:00
Kyle M Hall 8023b38202
Bug 40901: (follow-up) koha-create enables koha@<inst>.target on systemd
Without this, freshly-created instances on a koha-systemd host do
not auto-start at next boot.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:49 +00:00
Kyle M Hall 9ce9a07460
Bug 40901: (follow-up) sip.enabled is an anti-pattern for systemd
Remove the use of sip.enabled for systemd, keep for sysv

No other service using a .enabled file is controlled via systemd or sysv

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:48 +00:00
Kyle M Hall 8dccb4cc37
Bug 40901: (follow-up) Use --config-dir for z3950_responder.pl
Documentation is wrong, -c doesn't work for z3950_responder.pl

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:47 +00:00
Kyle M Hall c82e55a5a5
Bug 40901: (follow-up) Fail loudly when koha-sip is enabled without flag file
The koha-sip@.service unit gates startup on
/var/lib/koha/<inst>/sip.enabled, the per-instance "I want SIP on"
flag file. `koha-sip --enable` creates that flag in the same call
that runs `systemctl enable`; running a bare `systemctl enable
koha-sip@<inst>.service` only flips the systemd half, so the unit
ends up enabled-but-refusing-to-start.

With ConditionPathExists the failure mode is silent and the unit
ends up "enabled" at the systemd level but "Active: inactive"
(dead) with "Condition: start condition failed" and the journal
logs aren't a big help figuring out the problem.

This patch replaces the silent skip with a loud failure with a helpful message:
ERROR: SIP not enabled for instance <inst>. Use: koha-sip --enable <inst>

SIPconfig.xml is gated by AssertPathExists if it's missing the unit fails.

Either failure path lands the unit in failed state, surfaced by 'systemctl --failed'

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:45 +00:00
Tomás Cohen Arazi d67d319b89
Bug 40901: (follow-up) Preserve 'service koha-common' UX with systemd
Adds PartOf=koha.target to koha@.target so that stopping or
restarting the global target propagates to all instance targets
and their services.

Ships a koha-common.service oneshot unit that delegates to
koha.target, so 'systemctl restart koha-common' works as the
drop-in replacement for the old 'service koha-common restart'.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:44 +00:00
Tomás Cohen Arazi bf8529fbd4
Bug 40901: (follow-up) Read service config from koha-conf.xml in systemd units
The systemd units were missing most of the parameters that the SysV
init functions read from koha-conf.xml. This caused wrong worker
counts, missing log files, ignored batch sizes, and other config
being silently dropped.

This patch adds koha-generate-env, a helper script that reads
koha-conf.xml and writes an EnvironmentFile for each service.
Each unit runs it as ExecStartPre and then uses the variables.

Changes per unit:
- koha-plack@: workers, max-requests, access/error logs,
  environment (development/deployment), per-instance psgi, -M FindBin
- koha-zebra@: loglevels, max_record_size, timestamp format
- koha-es-indexer@: batch_size
- koha-indexer@: INDEXER_TIMEOUT from /etc/default/koha-common
- koha-z3950@: config dir fallback to global, additional options

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:42 +00:00
Tomás Cohen Arazi d8b3d46634
Bug 40901: (follow-up) Fix parallel execution of SysV and systemd services
When upgrading from the old koha-common (pre-split) to the new
koha-core + koha-systemd packages, the SysV init script
/etc/init.d/koha-common is left behind as an orphan. The
systemd-sysv-generator auto-creates koha-common.service from it,
which runs in parallel with the native systemd units, causing
double-starts of all Koha services.

Changes:
- koha-systemd.postinst now stops, disables, and removes the
  orphaned SysV init script when koha-sysv is not installed
- koha-sysv gets Replaces/Breaks on koha-common (<< 25.11) so
  dpkg can cleanly take over the init script file

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:41 +00:00
Tomás Cohen Arazi 6763a3048b
Bug 40901: (follow-up) Enable systemd units for existing instances on install
On first install of koha-systemd (migration from SysV), enable
units for all existing Koha instances. Core services (plack, zebra,
workers) are always enabled. SIP, Z39.50, and indexers are enabled
conditionally based on instance configuration.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:40 +00:00
Tomás Cohen Arazi af44d814af
Bug 40901: (follow-up) Add Replaces/Breaks for smooth upgrade from koha-common
koha-core takes over files previously owned by koha-common
(e.g. /etc/default/koha-common). Without Replaces/Breaks, dpkg
refuses to overwrite them during upgrade.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:38 +00:00
Tomás Cohen Arazi bde94861c4
Bug 40901: (follow-up) Regenerate debian/control from control.in
Run debian/update-control to include koha-systemd and koha-sysv
package stanzas and refresh Perl autodependencies.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:37 +00:00
Tomás Cohen Arazi 439be05aef
Bug 40901: (follow-up) Extract koha-create-dirs into a oneshot unit
Replace per-service ExecStartPre=+koha-create-dirs (running as root
on every start of every service) with a single oneshot unit per
instance. Services declare Requires= on it, and the oneshot's Before=
ensures directories exist before any service starts.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:35 +00:00
Tomás Cohen Arazi 90825d3292
Bug 40901: (follow-up) Fix incomplete systemd wiring in helpers
- koha-z3950-responder: add systemd enable/disable to enable_z3950/disable_z3950
- koha-disable: add plack and es-indexer stop/disable
- koha-remove: add worker --disable after --stop
- koha-systemd-ctl: fix service name worker-long to worker-long_tasks

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:34 +00:00
Tomás Cohen Arazi 630b1c377d
Bug 40901: (follow-up) Fix critical bugs in systemd integration
- koha-sip: disable_sip() was calling 'enable' instead of 'disable'
- koha-indexer: enable/disable dispatch was calling start/stop functions
- koha-systemd.postinst: upgrade try-restart was outside the case block

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:33 +00:00
Jake Bateman 142ab7320f
Bug 40901: Add systemd try-restart to koha-systemd postinst
In order for apt upgrades to trigger a service restart for all koha-systemd subservices and all instances, this adds a wildcard try-restart for those in addition to the daemon-reload. Only services which are already present and running will be restarted at postinst time

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:32 +00:00
Jake Deery d998990d77
Bug 40901: (testing follow-up) Add koha-create-dirs to units
This patch adds an elevated koha-create-dirs to the systemd units, so
that when the units (re)start, the directories are definitely present.
We found that PID files were missing and couldn't be created without
this patch.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:30 +00:00
Jake Deery 3073a347cc
Bug 40901: (testing follow-up) Added enable / disable to more helpers
This patch adds more enable / disable flags to the koha daemon helper
scripts. This is essential as systemd does not enable units by default,
especially when using instantiation variables.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:29 +00:00
Jake Deery d28ded7538
Bug 40901: (testing follow-up) Move koha-worker-long@ unit to koha-worker-long_tasks@
We found a bug when running koha-create. The systemd unit name didn't
match expected, so we've moved the unit to match the koha functions
computed value.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:28 +00:00
Jake Deery 0af4e80aa0
Bug 40901: (testing follow-up) Moved legacy code to koha-sysv.postinst
This patch moves the update-rc.d lines from koha-core to koha-sysv, so
it only fires if systemd is not in use

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:27 +00:00
Jake Deery 80e6ddaa90
Bug 40901: (testing follow-up) Remove / rename koha-common to koha-core
This patch removes or renames some of the debian files from koha-common
to koha-core, to avoid file clash during install

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:25 +00:00
Tomás Cohen Arazi a0f36621ad
Bug 40901: Add support for 'systemctl' in `koha-*` scripts
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:24 +00:00
Tomás Cohen Arazi be89db235c
Bug 40901: Restructure packages - koha-common becomes metapackage
This patch restructures the Koha packaging to support both systemd and
SysV init systems through a clean separation of concerns.

Package hierarchy:
- koha-core: Core application files (no init system)
- koha-systemd: Native systemd units (Provides: koha-init)
- koha-sysv: SysV init scripts (Provides: koha-init, Conflicts: koha-systemd)
- koha-common: Metapackage (Depends: koha-core, koha-systemd | koha-sysv)

Changes:
- koha-core: Removed 'daemon' dependency, updated description to clarify
  it provides core files without init system
- koha-systemd: Now depends on koha-core instead of koha-common, provides
  koha-init virtual package
- koha-sysv: Now depends on koha-core instead of koha-common, provides
  koha-init virtual package
- koha-common: Converted to metapackage that depends on koha-core and
  prefers koha-systemd on systemd-based systems
- Removed koha-common.install (files now installed by koha-core)
- Simplified koha-common.postinst (metapackage has minimal logic)

Benefits:
- Clean separation between core application and init system
- Users can explicitly choose their init system
- KTD can use koha-sysv without systemd
- Modern systems get native systemd by default
- Conflicts prevent both init systems being active simultaneously

Test plan:
1. Build packages
2. Install koha-common on a systemd system, verify koha-systemd is pulled in
3. Try installing both koha-systemd and koha-sysv, verify conflict
4. Install koha-core + koha-sysv explicitly, verify it works

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:23 +00:00
Tomás Cohen Arazi 34ad12360f
Bug 40901: Add koha-sysv package for SysV init support
This patch introduces the koha-sysv package, which provides traditional
SysV init.d scripts for managing Koha instances. All services (Plack,
Zebra, SIP, Z3950, workers, indexers) are managed through a single init
script using the daemon wrapper.

The package includes:
- /etc/init.d/koha-common (SysV init script)
- /lib/systemd/system/koha-common.service (systemd wrapper for init.d)

This package is intended for legacy systems, containers without systemd
(like KTD), or environments where SysV init is preferred.

The package provides 'koha-init' and conflicts with koha-systemd to
ensure only one init system is active at a time.

Test plan:
1. Build and install the package
2. Start services with /etc/init.d/koha-common start
3. Verify all services start correctly
4. Check that koha-systemd cannot be installed alongside

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:22 +00:00
Tomás Cohen Arazi dc8cced6be
Bug 40901: Add koha-systemd package with native systemd units
This patch introduces the koha-systemd package, which provides native
systemd service units for managing Koha instances. Each service type
(Plack, Zebra, SIP, Z3950, workers, indexers) is managed as a separate
systemd unit using instance templates.

The package includes:
- 8 service templates (plack, zebra, sip, z3950, worker, worker-long,
  indexer, es-indexer)
- 2 target units (koha@.target for per-instance, koha.target for global)
- Helper script (koha-systemd-ctl) for managing services

All service units include:
- KOHA_HOME environment variable pointing to /usr/share/koha
- WorkingDirectory set to /var/lib/koha/%i
- Proper user/group permissions (%i-koha)

This allows granular control over individual services per instance and
leverages systemd features like dependency management, resource limits,
and journald logging.

Test plan:
1. Build and install the package
2. Enable services for an instance
3. Verify all services start correctly
4. Check journalctl logs show no errors

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-29 09:01:21 +00:00
Pedro Amorim 02e0b4dc9e
Bug 42490: DBRev 26.06.00.030
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:36:08 +00:00
Andrew Fuerste Henry 20223a3e16
Bug 42490: (QA follow-up) Reword syspref description and options
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:59 +00:00
Martin Renvoize d8c245b505
Bug 42490: (QA follow-up) Address QA review comments
- Remove leftover skeleton boilerplate comments in the atomicupdate
  ("Do you stuffs here", "# sysprefs")
- Fix typo in atomicupdate description ("preferene" -> "preference")
- Update POD on MapItemsToHoldRequests to mention the new
  LocalHoldsPriorityScope gate
- Explicitly mock LocalHoldsPriorityScope in the existing Local Holds
  Priority subtests that exercise CreateQueue/MapItemsToHoldRequests,
  instead of relying on it falling through to the real DB default

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:58 +00:00
Andrew Fuerste Henry fa3aacffd8
Bug 42490: Update unit tests
Prove t/db_dependent/HoldsQueue.t

Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:57 +00:00
Andrew Fuerste Henry 662de2a955
Bug 42490: Implement LocalHoldsPriorityScope
To test:
1 - Set LocalHoldsPriority to "Don't give", set HoldsQueuePrioritizeBranch to "home library"
2 - have a bib with two items: both items available, Item 1 home and holdingbranch Centerville, Item 2 home and holdingbranch Fairfield
3 - place a hold (Hold 1) on the bib for a patron from Centerville , for pickup at Fairfield
4 - place a hold (Hold 2) on the bib for a patron from Fairfield, for pickup at Centerville

Establish behavior without LocalHoldsPriority
5 - build holds queue
6 - confirm the queue targets the FPL item for Hold 1 and the CPL item for Hold 2
7 - check both items out to a third patron (not one of your patrons with holds)
8 - check in Item 2, confirm it tries to fill Hold 1, click Ignore
9 - check in Item 1, confirm it also tries to fill Holds 1, click Ignore
10 - reset item locations: UPDATE items SET holdingbranch=homebranch;

Establish behavior with LocalHoldsPriority
11 - Set LocalHoldsPriority to "Give library", LocalHoldsPriorityPatronControl to "home library", LocalHoldsPriorityItemControl to "home library"
12 - build holds queue
13 - confirm the queue targets the CPL item for Hold 1 and the FPL item for Hold 2
14 - check both items out to a third patron (not one of your patrons with holds)
15 - build holds queue, confirm it has no targets
16 - check in Item 2, confirm it tries to fill Hold 2, click Ignore
17 - check in Item 1, confirm it tries to fill Holds 1, click Ignore
18 - reset item locations: UPDATE items SET holdingbranch=homebranch;

Apply patch, updatedatabase, restart_all
19 - confirm new system preference LocalHoldsPriorityScope, set to "checking items in and building the holds queue"
20 - repeat steps 11 - 18, no change in behavior
21 - set LocalHoldsPriorityScope to "checking items in"
22 - build holds queue
23 - confirm the queue targets the FPL item for Hold 1 and the CPL item for Hold 2
24 - check both items out to a third patron (not one of your patrons with holds)
25 - build holds queue, confirm it has no targets
26 - check in Item 2, confirm it tries to fill Hold 2
27 - check in Item 1, confirm it tries to fill Holds 1

Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:56 +00:00
Andrew Fuerste Henry 5434465a35
Bug 42490: Add system preference LocalHoldsPriorityScope
Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:54 +00:00
Slava Shishkin 08333dc996
Bug 42505: (follow-up) Generate hold slip after confirming hold
The previous follow-up passed the checked-in itemnumber to the slip, but
the slip was still rendered before ModReserveAffect() confirmed the
hold. Fields updated during confirmation, such as waitingdate and
expirationdate, were therefore missing or stale on the first print.

Open a blank named popup during the user gesture, then submit the
confirmation form with print_slip set. The response page reuses that
popup after the hold has been updated.

This preserves the popup blocker fix and allows ReserveSlip() to use
the persisted hold and item data without passing individual fields as
workarounds.

To test:
1. Set HoldsAutoFill to "Don't".
2. Add the following to the HOLD_SLIP notice:
   Waiting since: [% hold.waitingdate | $KohaDates %]
   Barcode: <<items.barcode>>
3. Place a title-level "Next available" hold with pickup at the
   check-in library.
4. Check in a matching item.
5. Click "Print slip and confirm".
6. Confirm the popup is not blocked.
7. Confirm the first slip contains the item's barcode and today's
   waiting date.
8. Reload the slip and confirm the values are unchanged.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:53 +00:00
Martin Renvoize 3ce02836d0
Bug 42505: Open hold transfer slip in user gesture to bypass popup blocker
When returning an item required to fulfil a hold at another branch with
HoldsAutoFill set to "Don't", the librarian is shown a "Hold found"
modal and chooses "Print slip, transfer, and confirm". The previous
implementation set a hidden 'print_slip' input to 1, submitted the
form, and relied on $(document).ready on the response page to call
Dopop() / window.open() to open the slip. Because that window.open()
is no longer associated with a user gesture (the gesture chain is
broken by the form submission and page navigation), modern browsers
block it as an unsolicited popup. The hold is confirmed but the slip
never appears, and the librarian may not realise it was attempted.

This patch opens the slip synchronously inside the .print click
handler instead, while the user gesture is still active, so the
browser allows the window.open() through. The form is then submitted
as before to confirm the hold. The reserve_id is taken from the
hidden input already present in the modal form; hold-transfer-slip.pl
needs only the reserve_id and does not require the hold to have been
moved to "Waiting" state first, so the slip content is correct.

To test:
1. Set syspref HoldsAutoFill = "Don't".
2. Place a hold for a patron whose pickup branch is different from
   your logged-in branch.
3. Check in the item at your logged-in branch.
4. The "Hold found" modal appears.
5. Click "Print slip, transfer, and confirm".
6. Confirm the hold transfer slip opens in a new window and is not
   blocked by the browser's popup blocker.
7. Confirm the hold is still correctly confirmed and the item placed
   in transit.
8. Repeat with HoldsAutoFill = "Don't" and a same-branch hold,
   clicking "Print slip and confirm" instead, and confirm the slip
   opens for that path too.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:52 +00:00
Pedro Amorim f1955d23f0
Bug 41253: DBRev 26.06.00.029
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:51 +00:00
Alex Carver [Acerock7] 03a817759c
Bug 41253: Fix Cataloging Side Nav
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:50 +00:00
Jonathan Druart 1d0e6f0930
Bug 41253: Grant permission only when the permission is created
In case it got backported, we don't want to grant permissions again,
only when the db rev is run for the first time.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:48 +00:00
Alex Carver [Acerock7] 9403a95104
Bug 41253: Item Modification by Age should have its own permission
Adds a new permission to control access to the Item Modification by Age tool in the Cataloging Module.

Previously access to this tool was controlled by the items_batchmod permission. This can be an issue for consortiums
where staff members should be able to batch change items that belong to their library, but who shouldn't be able
to define item aging rules. Staff with automatic item aging tool access can use authorized values to change items
that belong to other libraries.

This patch introduces a dedicated permission (items_modification_by_age) to restrict access to the tool to users
with this permission.

- new permission definition via atomicupdate
- removal of dependency on items_batchmod for tool access
- updated permission UI display
- updated cataloguing-home button visibility
- updated flagsrequired check in tool script

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-28 15:29:45 +00:00
Martin Renvoize 17cd7f3517
Bug 31925: Document that thesaurus-aware linking depends on index freshness
QA (comment 58/60) suggested LinkerConsiderThesaurus only works with
Elasticsearch, having observed duplicate authorities being created when
testing with Zebra. Reproduced this directly: with AutoCreateAuthorities
and LinkerConsiderThesaurus on, saving two bibs with the same new FAST
heading back-to-back (no pause) creates a duplicate authority under
Zebra, but waiting for Zebra's indexing daemon to catch up (a few
seconds) between saves lets the second heading link to the first-created
authority correctly, as designed.

The same duplicate-on-rapid-succession behaviour also reproduces with
LinkerConsiderThesaurus off and a plain LCSH heading - it's a pre-existing
race between AutoCreateAuthorities and Zebra's asynchronous indexing
daemon, not something specific to thesaurus-aware matching or this bug.
Elasticsearch indexes near-synchronously within the same request, so the
same race window is far less likely to be hit there, which is almost
certainly why this only showed up in manual Zebra testing.

This documents that dependency on the LinkerConsiderThesaurus system
preference description, rather than gating the feature to Elasticsearch
only, since the underlying logic is search-engine agnostic and works
correctly on Zebra given normal indexing latency.

Sponsored-by: Heythrop Library <https://heythroplibrary.co.uk/>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:51 +00:00
Martin Renvoize b0c502407d
Bug 31925: Only code auto-created authorities with the source thesaurus when LinkerConsiderThesaurus is on
Comments 43-51 identified a further problem with unconditionally coding
a newly auto-created authority's 008/11 (and 040$f) from the source bib
heading's thesaurus: when LinkerConsiderThesaurus is off (the default),
the linker matches new headings to existing authorities by text alone,
regardless of thesaurus. So a bib heading from one thesaurus (e.g. FAST)
can auto-create an authority coded for that thesaurus, and a later bib
heading with the *same* text but no thesaurus of its own (or a different
one) can then link to that same authority purely on the text match.
Saving that authority afterwards propagates its 008/11 and 040$f coding
back onto every bib heading linked to it - silently adding indicators and
subfields the cataloguer never set, on installs that never opted into
thesaurus-aware behaviour.

Before this bug, AutoCreateAuthorities always created LCSH-coded records,
so that propagation was always onto the coding most sites already expect.
This bug's earlier patches changed that default for every site regardless
of whether they use LinkerConsiderThesaurus, which is what comments 43/45
(reproduced by Pedro Amorim) demonstrated as disruptive.

Sites that haven't turned on LinkerConsiderThesaurus don't benefit from
thesaurus-aware authority creation anyway, since their linker never
searches by thesaurus - C4::Heading::_search and C4::Linker::Default
already gate thesaurus-consideration on this same preference for
matching. This change gates authority-creation coding on it too, so the
behaviour introduced by this bug only applies to installs that already
opted into thesaurus-aware linking.

Test plan:
1. prove t/db_dependent/Biblio.t
   Note the new case 7 in the "codes new authorities with the source
   thesaurus" subtest: with LinkerConsiderThesaurus off, a FAST heading
   no longer codes the new authority as FAST, it keeps the
   MARCAuthorityControlField008 syspref default instead.
2. Librarian-facing regression check: with LinkerConsiderThesaurus off
   (the default), AutoLinkBiblios and AutoCreateAuthorities on, catalogue
   a 650 with ind2=7 $2=fast and save. The new authority's 008/11 should
   be whatever MARCAuthorityControlField008 says (LCSH 'a' by default),
   not 'z'.
3. Confirm the LinkerConsiderThesaurus=on cases from the earlier patches
   are unaffected: with it on, a FAST heading still codes 008/11='z' with
   040$f=fast, and a MeSH heading still codes 008/11='c'.

Sponsored-by: Heythrop Library <https://heythroplibrary.co.uk/>
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:50 +00:00
Martin Renvoize c15b42c457
Bug 31925: Don't override 008/11 when the heading's thesaurus wasn't identified
QA (comments 23-30) flagged a regression in the previous patches: the
guard around coding a new authority's 008/11 (and 040$f) from the source
heading's thesaurus was `if ( $heading->{thesaurus} )`, but
C4::Heading::MARC21::_get_subject_thesaurus() never returns a false
value - a heading with a blank/unrecognised indicator 2 (by far the
commonest real-world case) comes back as the placeholder string
"notdefined", and indicator 2 = 4 comes back as "notspecified". Both are
truthy, so the override always fired, unconditionally coding every new
authority's 008/11 as 'z' or '|' instead of respecting the site's
MARCAuthorityControlField008 default (which defaults to 'a'/LCSH) - a
disruptive behaviour change for the majority of headings, not just the
non-LCSH ones this bug is meant to fix.

Adds Koha::Authority::$MARC21_UNDEFINED_THESAURUS_VALUES, the subset of
the existing thesaurus table's keys ('notdefined', 'notspecified') that
mean "no thesaurus could be identified" rather than a genuine one, and
uses it in LinkBibHeadingsToAuthorities()'s guard so those two values now
fall through to the site's own syspref default, unchanged, exactly as
before this bug's patches.

Test plan:
1. prove t/db_dependent/Biblio.t
   Note the two new cases in the "codes new authorities with the source
   thesaurus" subtest: a blank-indicator-2 heading now keeps 008/11 at
   the syspref default ('a') instead of being forced to 'z', and an
   indicator-2=4 heading respects a customised syspref default instead
   of being forced to '|'.
2. Librarian-facing regression check: with AutoLinkBiblios and
   AutoCreateAuthorities on, catalogue a 650 with a blank/space
   indicator 2 (e.g. `650 __ $a Some topic`, no $2) and save. The new
   authority's 008 position 11 should read whatever
   MARCAuthorityControlField008 says (LCSH 'a' by default), not 'z'.
3. Confirm the non-LCSH cases from the earlier patches are unaffected:
   a FAST heading (ind2=7, $2=fast) still codes 008/11='z' with
   040$f=fast, and a MeSH heading (ind2=2) still codes 008/11='c'.

Sponsored-by: Heythrop Library <https://heythroplibrary.co.uk/>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:49 +00:00
Martin Renvoize 65dac0fb94
Bug 31925: AutoCreateAuthorities must preserve subject heading thesaurus
When AutoCreateAuthorities creates a new authority for a bib subject
heading that didn't match an existing one, it never set 008 or 040 on the
new MARC::Record, so C4::AuthoritiesMarc::AddAuthority()'s own defaulting
always ran and hardcoded 008/11 to 'a' (LCSH), regardless of the source
heading's real thesaurus. A FAST, MeSH, or other non-LCSH heading (MARC
indicator 2 = 7 with subfield $2, or indicator 2 = 1-6) therefore always
got mis-coded as LCSH on creation. Because the authority was permanently
mis-coded, it could never be found again by thesaurus-aware matching
(LinkerConsiderThesaurus), so every later import of the same non-LCSH
heading created yet another duplicate authority instead of linking to the
one already created (see bug 42694 for a real-world report of this).

This calls Koha::Authority->default_marc21_008($thesaurus) to build the
new authority's 008 - correctly coding position 11 for the source
heading's actual thesaurus - and, for headings identified only by a raw
$2 code, adds a 040$f before calling AddAuthority(), whose existing "only
default if the field isn't already there" guards then leave our
correctly-coded fields alone. No change to AddAuthority() itself is
needed for this. This is MARC21-only (matches the existing marcflavour
branch already in this code path) and ships as an unconditional bug fix,
with no new system preference: it only changes how a *new* authority is
coded going forward and does not touch or merge any existing authority
records.

Test plan (librarian-facing):
1. Turn on the AutoLinkBiblios and AutoCreateAuthorities system
   preferences (Administration -> System preferences -> Cataloging).
2. Catalog a new bibliographic record, or edit an existing one, and add a
   650 field coded for a non-LCSH thesaurus, e.g.:
     650 _7 $a Miracles $2 fast
   (2nd indicator = 7, subfield $2 = fast; a FAST heading with no
   existing matching authority in your catalogue).
3. Save the record. Koha auto-creates a new "Miracles" authority record,
   since none existed yet to link to.
4. In the Authorities module, open the newly created "Miracles" record
   for editing and switch to MARC view (Edit -> Edit record, or the "..."
   menu -> Edit as new).
5. Click into the 008 field to open its editor plugin.
   Before this fix: the "11- Subject heading system/thesaurus" dropdown
   is set to "a- Library of Congress Subject Headings", and field 040 has
   no $f subfield.
   After this fix: that dropdown is now set to "z- Other", and field 040
   has a $f subfield containing "fast" — correctly recording the real
   source vocabulary instead of silently mis-labelling it as LCSH.
6. As a regression check, repeat steps 2-5 with a plain LCSH heading
   instead, e.g.:
     650 __0 $a Feminism
   (2nd indicator = 0, no $2). Confirm the resulting authority's 008 still
   shows "a- Library of Congress Subject Headings" as before — this fix
   must not change behaviour for ordinary LCSH headings.
7. Optional, Elasticsearch installations only: with LinkerConsiderThesaurus
   also turned on, re-import or re-save a bib with the same FAST heading
   from step 2 a second time. It should now link to the authority created
   in step 3 instead of creating a second duplicate — this is the actual
   duplicate-authority problem reported in bug 42694.

Sponsored-by: Heythrop Library <https://heythroplibrary.co.uk/>
Signed-off-by: Clemens Gresser <cgresser@jesuit.org.uk>
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:48 +00:00
Martin Renvoize 9186341ddd
Bug 31925: Make QueryBuilder reuse the shared thesaurus/008 lookup
Koha::SearchEngine::Elasticsearch::QueryBuilder kept its own copy of the
thesaurus-name to 008/11-code table used when building "thesaurus" search
queries against authority records. The previous commit moved that same
table to Koha::Authority as a shared lookup. This commit deletes
QueryBuilder's duplicate and points $thesaurus_to_value at
$Koha::Authority::MARC21_THESAURUS_TO_CONTROL_FIELD_008_11 instead, so
there is only one place to update if the code list ever changes.

Pure refactor: $thesaurus_to_value keeps its existing name and contents,
and build_authorities_query_compat() is unchanged, so query behaviour is
identical before and after.

Sponsored-by: Heythrop Library <https://heythroplibrary.co.uk/>
Signed-off-by: Clemens Gresser <cgresser@jesuit.org.uk>
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:47 +00:00
Martin Renvoize 4bfb9abc68
Bug 31925: Add thesaurus/008 lookup and default MARC21 008 builder to Koha::Authority
AutoCreateAuthorities builds a brand-new authority record whenever a bib
subject heading (6XX) has no match, but always lets the record fall back
to C4::AuthoritiesMarc::AddAuthority()'s default 008/040, which hardcodes
008 position 11 ("Subject heading system/thesaurus") to 'a' (LCSH) no
matter what thesaurus the source heading actually uses. This is the first
step towards fixing that.

Adds two things to Koha::Authority:

- $MARC21_THESAURUS_TO_CONTROL_FIELD_008_11: a single-source-of-truth
  lookup mapping a thesaurus name (as already derived by C4::Heading from
  MARC indicator 2 / subfield $2, e.g. 'lcsh', 'mesh', or a raw $2 code
  such as 'fast') to its MARC21 authority 008/11 code, per the LOC ad008
  code list. This table previously only existed once, for a different
  purpose, in Koha::SearchEngine::Elasticsearch::QueryBuilder (used for
  building thesaurus search queries) - it's promoted here so both that
  and the authority-creation fix that follows can read from one place
  instead of two copies that can silently drift apart.

- default_marc21_008($thesaurus): builds the default 34-character body of
  a new MARC21 authority's 008 field, honouring the
  MARCAuthorityControlField008 system preference. When $thesaurus is
  given, position 11 is set from the table above instead of whatever the
  default/syspref value carries, falling back to 'z' (Other) for a
  thesaurus with no dedicated code.

AddAuthority() is refactored to build its own (thesaurus-less, LCSH
default) 008 via this new method too, replacing its previous inline copy
of the same defaulting logic - pure refactor, no behaviour change.
Nothing thesaurus-aware calls it yet.

Sponsored-by: Heythrop Library <https://heythroplibrary.co.uk/>
Signed-off-by: Clemens Gresser <cgresser@jesuit.org.uk>
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:46 +00:00
Tomás Cohen Arazi 9eaeae69e5
Bug 42719: Handle missing CGISESSID in OAuth login (IdP-initiated flow)
When a user initiates login from the IdP (e.g. Okta app tile), they
hit the OAuth endpoint without an existing session cookie. The code
crashed calling ->value on an undefined cookie object.

This patch:
- Removes the premature 'No user session found' guard that blocked
  IdP-initiated flows entirely
- Creates a session on-the-fly for the initial request when no
  cookie exists (IdP-initiated flow)
- On callback, redirects with wrong_csrf_token error instead of
  crashing when the cookie is missing

CSRF protection is preserved: the state token is bound to the
session created in the initial request, and the browser returns
the cookie on callback for validation.

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:45 +00:00
Tomás Cohen Arazi fda4343019
Bug 42719: Add regression tests
Add tests for the OAuth/OIDC login endpoint covering:
- IdP-initiated flow (no CGISESSID cookie on initial request)
- Callback without CGISESSID cookie (session lost)
- Normal SP-initiated flow (cookie present)

Tests create a real identity provider in the database and assert:
- Initial request without cookie redirects to the IdP authorize URL
- A CGISESSID cookie is set for the new session
- Callback without cookie returns wrong_csrf_token error (not 500)
- Normal flow with cookie redirects to IdP as expected

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:44 +00:00
Jonathan Druart 2db06f9510
Bug 43561: Redirect to local use sysprefs instead of 'not found'
If a user search for a local use syspref, the name will appear in the
autocomplete but when choosen "not found" will be return.  This patch
suggests to redirect to the "local use" syspref view if we found a local
use syspref that match the search term.

Test plan:
- in admin, type "OPACdid" into the system preference search
- see "OPACdidyoumean" is offered as a search term
- click OPACdidyoumean
=> Without this patch you get an error that no such syspref exists
=> With this patch you are redirected to the local use syspref view

Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: John Vinke <jvinke@austinseminary.edu>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:43 +00:00
Martin Renvoize f8601c1b0f
Bug 43288: Fix change amount missing from printed fee receipts
pay.tt, paycollect.tt and boraccount.tt all open printfeercpt.pl with
a change_given= query parameter after a payment, but printfeercpt.pl
only ever reads a 'change' param into its GetPreparedLetter substitute
hash - matching the ACCOUNT_CREDIT/CREDIT_* default notice templates,
which use [% change | $Price %], not [% change_given | $Price %].

As a result the change amount is silently blank on every printed fee
receipt that shows it, regardless of FinePaymentAutoPopup.

Rather than touch printfeercpt.pl or the notice content (both already
agree on 'change', matching a similar migration Koha did once before
in installer/data/mysql/db_revs/220600072.pl going the same direction),
fix the three query strings that build the printfeercpt.pl URL to
send change= instead of change_given=. The change_given variable name
is kept everywhere else (form fields, JS locals, Perl params) since
this is purely about the query key at the printfeercpt.pl boundary.

Test plan:
1) Set FinePaymentAutoPopup to 'show'
2) Take a payment for a patron via members/pay.pl or members/paycollect.pl
   that generates change (fine amount not evenly covered by cash tendered)
3) Note the auto-popped-up receipt shows the change amount
   (before this patch, the Change line was always blank)
4) Repeat via the boraccount.pl > printfeercpt.pl print receipt route

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:41 +00:00
Jonathan Druart 7bba6e855a
Bug 42697: Add Cypress tests
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:40 +00:00
Jonathan Druart ab1a20b3c8
Bug 42697: Revert "Bug 39011: Apply the pre-process to column filters"
This reverts commit 4fcff9db9e.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:39 +00:00
Lawrence O'Regan-Lloyd c893f47bb3
Bug 41445: Fix cleanup after single hold is suspended
This fixes information displayed in the holds table for a patron after a single hold is suspended (in the "Check out" and "Details" sections for a patron record in the staff interface).

After suspending a hold, no holds are selected in the holds table, the select_hold_all checkbox is unchecked, and the holds select badge counter is 0.

Test plan:

0. Apply patch
1. Go to a patron with 2 or more active holds
2. Navigate to Details -> Holds tab
3. Click Suspend on a single hold and confirm
4. Verify the select-all checkbox remains unchecked
5. Verify the selected holds count is set to 0
6. Repeat steps 3-5 from the Check out -> Holds tab

Assisted-by: Opus 5 (Anthropic)
Sponsored-by: CLAMS
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-25 12:08:35 +00:00
Nick Clemens 1b408f7bb2
Bug 36549: (QA follow-up) Restore sleep
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-24 15:48:32 +00:00
Nick Clemens 6b7db3cfb4
Bug 36549: Use Parallel::ForkManager in es_indexer_daemon
This patch copies the implementation from the general background
worked into the es_indexer daemon

Note: There is a 'sleep(10)' in the general indexer and I copied it here,
but I don't know why we do it?

To test:
1 - Apply the patch
2 - sudo koha-es-indexer --restart kohadev
3 - perl misc/maintenance/touch_all_biblios.pl
4 - Wait a bit
5 - Check the background jobs in the admin interface
6 - Confirm the jobs finish and records are searchable
7 - Test with and without rabbitmq enabled:
    . Stop rabbitmq service: sudo -s service rabbitmq-server stop
    . Repeat steps 2 to 5
8 - Sign off

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-24 15:48:31 +00:00
Lawrence O'Regan-Lloyd 7a5c6b6066
Bug 43513: Add .mailmap entries for Lawrence O'Regan-Lloyd
This patch adds two entries mapping both addresses I commit from to a single
identity, so the generated files pick up the right name the next time
they are regenerated.

Test plan:
1. Apply patch
2. Run: git check-mailmap "lawrenceol-clams <loreganlloyd@clamsnet.org>"
=> SUCCESS: returns Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>
3. Run: git check-mailmap "lawrenceol <hello@lawrenceol.dev>"
=> SUCCESS: returns Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>
4. Run: git shortlog -sne | grep -i "regan-lloyd"
=> SUCCESS: a single entry, not two

Assisted-By: Claude Opus 5 (Anthropic)
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-24 15:48:29 +00:00
Casey Conlin fb98e7c809
Bug 43591: Bring back my libraries filter.
This is a patch to bring back a small UI component that was dropped on the previously QA’ed and merged work in bug 16631.
Before applying patch, confirm the filter option for Show reports is not available on the Saved Reports page
1. Pull the latest from Main
2. Enable  LimitReportsByLibrary under Reports in System Prefernces
3. Visit the Save reports page
4. There should be NO Show reports with selectbox in the Filter pane on the left side
5. Implement the patch
6. Reload Save reports page
7. In the Filter pane on the left side, there should be a Show reports select box with options for Show my library reports and Show all reports options

Confirm only permissioned users see this option and reports are filtered
8. Create a staff user with the all reports permissions including Manage Report Limits (manage_report_limits). Home library should be Centerville.
9. Create a staff user with all reports permission but NOT Manage Report Limits (manage_report_limits). Home library should be Centerville.
10. Create a report, “No limit”  with no limit.
11. Create a report, “Centerville limit” limited to Centerville.
12. Create a report “Fairfield limit” limited to Fairfield.
13. Log in as the permissioned user.
14. Confirm the filter is available and that it filters reports so that only the No limit and Centerville limit reports show when My library reports only is selected.
15.Select All reports and all 3 reports should be in the list along with the Fairfield report .
16. Log in as the unpermissioned user.
17. The filter option for my / all reports should not be in the leftside pane.
18. Only the Centerville limit and No limit reports should be visible

Signed-off-by: Anna Stalter <staltera@hamiltoneastpl.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-24 15:48:28 +00:00
Owen Leonard b09bc3c948
Bug 43519: Direct links to article request form still work when the feature is disabled
This patch adds a check for the ArticleRequests system preference in
opac-request-article.pl so that the page will return a 404 error if the
preference is disabled.

To test, apply the patch and restart services.

- If necessary, enable the ArticleRequests system preference and
  configure a circulation rule to allow article requests.
- Locate a bibliographic record in the OPAC which will accept an article
  request. Note the biblionumber of the record you select.
  - Click "Request article" and complete the process. It should finish
    successfully.
- Disable the ArticleRequests system preference.
- Return to the OPAC and navigate directly to the page for requesting an
  article:
  http://localhost:8080/cgi-bin/koha/opac-request-article.pl?biblionumber=X
  ...where X is the biblionumber you noted before.
  - This should trigger a 404 error.

Sponsored-by: Athens County Public Libraries

Signed-off-by: Bastien CHAUVET <bastien.chauvet@inlibro.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-24 15:48:25 +00:00
Owen Leonard 04314c4fbc
Bug 38948: Modal error on some pages: Can't access property "backdrop"
This patch adds some checks to cookieconsent.js to avoid errors in the
console: If we try to access 'document.getElementById("cookieConsentModal")'
when the element doesn't exist there will be an error:

'Uncaught TypeError: can't access property "backdrop", this._config is
undefined'

The error can be seen in the browser console on the Circulation home
page and the Tools home page when the CookieConsent preference is
enabled.

To test apply the patch and clear your browser cache if necessary.

- Enable the CoookieConsent system preference.
- Go to the Circulation home page. There should be no error in the
  console.
- Clear your browser's local storage to forget your consent.
- Go to the staff client home page. The cookie consent message should
  appear.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-24 11:48:44 +00:00
Paul Derscheid 9e0b6c3914
Bug 42013: Fix missing confirmation dialog for "Remove all reserves"
- The confirmDelete dialog for removing all reserves from a course
  was bound as a click handler on the #rm_items form element, which
  is never directly clicked by the user
- The #rm_items_button click handler called .submit() on the form
  without checking confirmDelete first, bypassing the confirmation
- Move the confirmDelete check into the #rm_items_button click
  handler and remove the dead #rm_items click handler

Test plan:
- Enable UseCourseReserves system preference (should be enabled in ktd)
- Create a course (or use the existing one) and add at least one reserve
  to it
- On the course details page, click "Remove all reserves"
- Observe the form submits immediately without confirmation
- Apply patch
- Repeat the steps above
- Verify a confirmation dialog now appears
- Click cancel and verify the reserves are not removed
- Click "Remove all reserves" again, confirm, and verify
  the reserves are removed

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-24 11:48:43 +00:00
Lucas Gass cd3574bdda
Bug 43383: Use checkbox status of :checked when doing cartsubmit or listsubmit
To test:
1. Do a catalog search that brings up multiple results
2. Check the checkbox of in the left column for the first two results
3. Click on the title of the 3rd result to take you to the details page for that item
4. Use the borwser's back button to return to the search results
5. Notice the first 2 titles are still checked.
6. Now check the 3rd result and click 'Add to list'
7. Only the 3rd title is added, even though all 3 are checked.
8. Try steps 1 - 7 again but with 'Add to cart', same behavior.
9. Try setps 1 - 7 again but with 'Place hold', all 3 items are selected.
10. APPLY PATCH, clear browser cache and run through all the steps again. This time your selction should be rememebered.

Bonus points for trying in as many browsers as possible.

Signed-off-by: Bastien CHAUVET <bastien.chauvet@inlibro.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-24 11:48:41 +00:00
Martin Renvoize 3b24981b89
Bug 42608: (follow-up) Adapt tests to Bug 42310 calendar schema
Bug 42310 landed on main after this patch's tests were written. It
renamed Koha::Calendar to Koha::Library::Calendar and normalized the
legacy repeatable_holidays/special_holidays tables into
library_weekly_closures/library_single_closures (plus repeating
closures and exceptions), managed via Koha::Library::Calendar::*
DBIC-backed classes.

The new subtests added for this bug still referenced the old class
name and the dropped DBIC sources (RepeatableHoliday, SpecialHoliday),
so t/db_dependent/Calendar.t and t/db_dependent/Hold.t failed after
rebasing onto current main. Update both to build closures via
Koha::Library::Calendar::WeeklyClosures/SingleClosures and to
instantiate Koha::Library::Calendar, matching the convention already
used elsewhere in these files since the Bug 42310 follow-up.

No production code changes; test-only fix.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-24 11:48:40 +00:00
Martin Renvoize 58d41bfedf
Bug 42608: Stop hold pickup expiration from week-jumping over closures
Koha::Hold::set_waiting calls Koha::Calendar::days_forward to derive the
holds-shelf expiration date when ExcludeHolidaysFromMaxPickUpDelay is on.
days_forward iterated next_open_days($dt, 1) ReservesMaxPickUpDelay times,
and next_open_days consults get_push_amt for every closed day it
encounters. Under useDaysMode=Dayweek, get_push_amt returns 7 for any
closure that is not a permanently closed weekday -- so an intermediate
Bank Holiday Monday inside the pickup window pushed the cursor a whole
week forward instead of one day, inflating the expiration by seven days.

Dayweek is a loan-due-date concept: it exists to keep a Friday-due loan
due on a Friday across closures. A patron does not pick a weekday for a
hold-shelf collection; staff set the expiration when they trap the item,
and the only thing that should drive it is how many open days the patron
should have to come and collect. days_forward is now decoupled from
useDaysMode and always counts forward one open day at a time, ignoring
closures without consuming them from the count.

next_open_days, prev_open_days, get_push_amt and addDays are unchanged --
loan due-date math keeps the existing Dayweek behaviour.

Test plan:
1. Inside KTD, run the updated unit tests:

     prove t/db_dependent/Calendar.t
     prove t/db_dependent/Hold.t

   Confirm both pass, including the new Bug 42608 sub-tests. These cover
   the full matrix of starting weekdays and useDaysMode values; the
   manual steps below are a single confirmatory pass through the UI.

2. Manual reproduction (UI only -- no database or CLI access needed):
   - Set sysprefs: useDaysMode=Dayweek, ReservesMaxPickUpDelay=6,
     ExcludeHolidaysFromMaxPickUpDelay=ON, ExpireReservesOnHolidays=ON.
   - Administration > Calendar: on a test branch, add one single
     (one-time) holiday -- not weekly/repeating -- on any weekday within
     the next 6 days that isn't already closed (e.g. the day after
     tomorrow, as long as it isn't a Sunday or other closed day). This
     one-off closure inside the pickup window is what triggers the bug.
   - Place a hold on an available item, then check the item in at that
     branch and confirm/trap it. This stamps waitingdate with today's
     date automatically -- no manual date entry required.
   - Check the hold's expiration date (Holds awaiting pickup list, or
     the patron's holds tab):
       Without the fix: expiration lands a full 7 days later than it
       should (today + 6 + 7), from the week-jump.
       With the fix: expiration is today + 6 open days, only skipping
       the one closed day.

3. Verify the existing Calendar mode behaviour is unchanged:
   - Switch useDaysMode to Calendar and repeat step 2 -- the same
     expected expiration date should apply.

4. Verify loan due-date behaviour is unchanged:
   - With useDaysMode=Dayweek, check out an item whose loan period would
     place its due date on the same closed day used in step 2. Confirm
     the due date still rolls forward by a whole week (the original
     Dayweek intent), proving this patch did not regress
     addDays/addDuration.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-24 11:48:37 +00:00
Owen Leonard 9c3d3306e3
Bug 43237: (follow-up) Adding [% PROCESS 'i18n.inc' %] to two templates which lack it
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 16:07:52 +00:00
Owen Leonard 3d0b88df00
Bug 43237: Add 'Expand' column header to all OPAC responsive DataTables
This patch updates several templates in the OPAC so that when a
responsive DataTable is partially collapsed, the column with the "+"
buttons has "Expand" in the column header.

To test, apply the patch and test tables on the affected pages,
narrowing the page enough to confirm that when the table collapses, the
last column has "Expand" in the header.:

- Authorities search results
- The details of a course reserve (with titles on reserve)
- The holdings table on the bibliographic detail page
- Search history
- Most popular

As a logged-in user:
- User summary page (all tabs)
- Curbside pickups (with at least one curbside pickup scheduled)
- Checkout history
- Suggestions
- Tags

In self-checkout:
- The checkouts, holds, and charges tabs.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 16:07:51 +00:00
Pedro Amorim d486b1c68b
Bug 43336: DBRev 26.06.00.028
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 16:07:50 +00:00
Magnus Enger 6d160313a2
Bug 43336: Remove support for LibrisSpellcheck
The API that this feature was based on was removed by the Swedish
National Library, so we can remove the code and sysprefs from Koha.

Before testing:
- Run this SQL in the database and verify the syspref is empty:
  SELECT value FROM systempreferences WHERE variable = 'OPACdidyoumean';
- Go to Administration > Did you mean? and verify there is a box for
  LibrisSpellcheck
- Move the boxes around and click on "Save configuration"
- Run the SQL again and verify the contents of the syspref are now
  something like this:
  [{ "name": "AuthorityFile"}, { "name": "LibrisSpellcheck"}, { "name": "ExplodedTerms"}]
- Go to Administration > System preferences and search for "libris"
- Verify the two sysprefs LibrisKey and LibrisURL show up

To test:
- Apply this patch and make sure the DB update is run. In KTD you can
  do:
  $ sudo koha-upgrade-schema kohadev
  The output from this should be like this:
	Removed system preference 'LibrisKey'
	Removed system preference 'LibrisURL'
	'LibrisSpellcheck' was removed from the OPACdidyoumean syspref
- Run koha-upgrade-schema again. The output should be:
	'LibrisKey' syspref already removed
	'LibrisURL' syspref already removed
	'OPACdidyoumean' syspref does not contain a setting for 'LibrisSpellcheck'
- Run the same SQL as before and verify that LibrisSpellcheck is now
  gone:
  [{"name":"AuthorityFile"},{"name":"ExplodedTerms"}]
- Go to Administration > Did you mean? and verify there is no box
  for LibrisSpellcheck
- Move the boxes around and click on "Save configuration"
- Run the SQL again and verify that there is still no mention of
  LibrisSpellcheck
- Go to Administration > System preferences and search for "libris"
- Verify the two sysprefs LibrisKey and LibrisURL do NOT show up
- Empty the OPACdidyoumean syspref by running this SQL:
  UPDATE systempreferences SET value = '' WHERE variable = 'OPACdidyoumean';
- Run koha-upgrade-schema again. The output should be:
	'LibrisKey' syspref already removed
	'LibrisURL' syspref already removed
	'OPACdidyoumean' is not set

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 16:07:48 +00:00
Jonathan Druart 632017e022
Bug 43267: Avoid flashing the wrong number of visible records
Wait before DT has finished the init to display the number of visible
records, otherwise there is a flash (display the count of the full set,
then the number of records displayed (20));

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 16:07:47 +00:00
Jonathan Druart 83f17afef6
Bug 43267: Disable "Batch operations" menu when there's nothing to send
So we don't send an empty set to the tool

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 16:07:46 +00:00
Jonathan Druart 26914173fd
Bug 43267: Reports - only send visible records to batch operations from DT view
When opening report results in the new DataTables view, batch
operations should only send visible records. Same as the normal view.

Test plan:
1. Create and run a report that will display more resulsts than fit on
   one page
2. Click "Open in DataTables".
3. Note "Batch operations with X visible records" shows the default
   page size (=20), and open the "Batch operations" dropdown to
   send results to e.g. batch item modification or batch record
   modification.
4. Confirm only the values on the currently visible page are sent, not
   the entire set.
5. Change the DataTable's page, or filter/search within it, and repeat
=> Confirm the count and the sent values update to the number of visible records
6. Confirm the standard (non-DataTables) report view is unaffected.

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 16:07:45 +00:00
Chloé Zermatten 18aa5d1f4c
Bug 10190: (QA follow-up): fixes phase six
1. add missing <tr> wrapper element
2. amend casing in cypress test
3. change method name so it is codespell-friendly
4. remove deprecated test from TestBuilder.t
   (used Overduerules which do not exist anymore.
    rely on 'Tests with composite FK in userpermission'
    for FK test coverage instead -> no replacement
    introduced.)
5. 'Bug 10190: Fix patron_categories permission format'
   changed the permission for GET requests to patron_categories
   to catalogue = 1, matching the Koha precedent for such
   endpoints. However, patron_categories still tested the
   endpoint with the old permission assigned. Amend this to
   complete the work started in that commit.
6. remove redundant delete statement in permission test:
   if the test fails, this means the db update has not
   run, and should. It would be incorrect for the db
   to still include the permission at the point where the
   test runs.

Test plan:

1. run `npm run js:build` (or watch the js:watch output)
2. run t/cypress/integration/Admin/CirculationTriggers_spec.ts
3. run prove xt/author/codespell.t
4. run prove t/db_dependent/TestBuilder.t
5. run prove t/db_dependent/api/v1/patron_categories.t
6. run prove t/Koha/Auth/Permissions.t

All should run with no warnings or errors.

Assisted-by: Claude Opus 5 (Anthropic)
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 16:07:44 +00:00
Chloé Zermatten c96618f064
Bug 10190: (QA follow-up): fixes phase five
1. apply html filter to letters so notice names are escaped (must
    be done in attribute so the JSON format of the letters can
    be parsed).
2. fix a correctness issue where the frontend was pinning the
   logged in library instead of the logged in user's home library,
   which would have led to 403s (correct) when saving rules. Since
   the library dropdown is deliberately disabled unless a user has
   the manage_circ_rules_from_any_libraries permission, the user
   would then have needed to use 'Set library' to match their home
   library to fix this. Prevent the issue by setting the library
   to the user's home library.
   Also rename the variable to user_library_id.
3. list_rules correctly references /api/v1/circulation_rules/kinds
4. make untranslatable strings translatable
5. swap svg for font-awesome icon.
6. avoid css duplication:
   - remove dead / unused scoped css (leftover from an early component
     extraction)
   - import vue.css and only leave the minimal override required.
7. improve set_rules test coverage: add tests for manage_circ_rules
   / manage_circ_triggers separation

Test plan:

1) Administration > Circulation triggers: notice the 'Letter' dropdowns
   list circulation notices as "name (code)" and offer 'No letter'.
   (no regression)
2) Give a staff user 'catalogue' + 'manage_circ_triggers' only, with
   home library A. Log in as them, 'Set library' to B, and open
   Administration > Circulation triggers. Notice the view is pinned to
   A, and that adding and editing a trigger saves without a 403.
   (behaviour is now correct)
3) GET /api/v1/circulation_rules?rules=nosuchrule - notice the error
   points at /api/v1/circulation_rules/kinds.
   (behaviour is now correct)
4) Run 'gulp po:update --lang fr-FR'. Notice 'Add new trigger
   {triggerNumber}', 'No letter' and 'any' now appear in
   misc/translator/po/fr-FR-messages-js.po
   (behaviour is now correct)
5) Edit a trigger. Notice the delay's reset button is an 'x' inside the
   input, left of the chevrons, and clears the delay; the letter's
   reset button does the same for the notice.
   (no regression)
6) Notice no visual regression across the list, add, edit, reset and
   delete screens - select widths, chevrons and modals unchanged.

Also run t/db_dependent/api/v1/circulation_rules.t

Assisted-by: Claude Opus 5 (Anthropic)
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 16:07:39 +00:00
Martin Renvoize 167622c08d
Bug 40934: (follow-up) Fix false positive on --code/--exclude-code mutual exclusivity
The mutual-exclusivity check ran before the empty-element cleanup added
for bug 37075, so a bare -c (no value) left an empty string in
@letter_code, which the check saw as truthy and rejected even though
-c had effectively not been used. Move the cleanup above the check.

Also add the same mutual-exclusivity guard directly in
C4::Letters::SendQueuedMessages, since letter_code and
exclude_letter_code build the same search key and passing both would
otherwise let one silently clobber the other in the hashref passed to
->search - only the CLI script enforced this until now, so any other
caller could hit the silent-clobber bug.

Adds regression coverage: unit tests for the new exception in
SendQueuedMessages, and a script-level test reproducing the RM's
reported false positive.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:49 +00:00
Nick Clemens 2a652a8d12
Bug 40934: (QA follow-up) Don't delete the queue
We already delete the queue at the start of all tests and the tests
here are checking using ID, there is no reason to delete the queue again

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:48 +00:00
Martin Renvoize e6908ef0a7
Bug 40934: (follow-up) --code and --exclude-code should be mutually exclusive
Both options were being accepted but only the last one in the command line
was being applied. Now they are mutually exclusive like --category and
--skip-category in longoverdue.pl.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:46 +00:00
Martin Renvoize 3430793f07
Bug 40934: Add --exclude-code option to process_message_queue.pl
Some libraries want to send digest messages once per day (e.g., at 20:05)
but send all other messages every hour. Currently, this requires listing
almost all letter codes (~85) in the hourly cron job, which is error-prone
and makes it easy to forget notifications at upgrades.

This patch adds a new --exclude-code (-x) option to process_message_queue.pl
that allows excluding specific letter codes from processing, making it much
easier to configure digest scheduling.

Changes:
- Added --exclude-code option to process_message_queue.pl (repeatable)
- Modified C4::Letters::SendQueuedMessages to handle exclude_letter_code parameter
- Supports both scalar and array reference formats

Example usage:
  # Send all messages except digests every hour
  00 */1 * * * process_message_queue.pl --exclude-code DUEDGST \
    --exclude-code PREDUEDGST --exclude-code HOLDDGST \
    --exclude-code AUTO_RENEWALS_DGST

  # Send all messages including digests once per day
  05 20 * * * process_message_queue.pl

Test plan:
1. Apply both patches
2. Run: ktd --shell --run 'prove t/db_dependent/Letters.t'
3. Verify all tests pass (should show 105 tests passing)
4. Create test messages in message_queue with different letter codes:
   - Insert messages with codes: DUEDGST, PREDUEDGST, ACQ_NOTIF, HOLD
5. Test single exclusion:
   misc/cronjobs/process_message_queue.pl --exclude-code DUEDGST
   - Verify DUEDGST messages remain pending
   - Verify other messages are processed
6. Test multiple exclusions:
   misc/cronjobs/process_message_queue.pl --exclude-code DUEDGST \
     --exclude-code PREDUEDGST
   - Verify both digest messages remain pending
   - Verify non-digest messages are processed
7. Test combined with --code option (should work together):
   misc/cronjobs/process_message_queue.pl --code ACQ_NOTIF \
     --exclude-code DUEDGST
   - Verify only ACQ_NOTIF messages processed (DUEDGST excluded anyway)
8. Test help output:
   misc/cronjobs/process_message_queue.pl --help
   - Verify --exclude-code option is documented

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:45 +00:00
Martin Renvoize 2bf60ebf21
Bug 40934: Add tests for exclude_letter_code in process_message_queue.pl
This patch adds unit tests for the new exclude_letter_code functionality
in SendQueuedMessages, which will allow excluding specific letter codes
when processing the message queue.

Test coverage includes:
- Excluding a single letter code (array format)
- Excluding multiple letter codes
- Excluding with scalar parameter format

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:44 +00:00
Slava Shishkin 0dfcddf383
Bug 43296: (follow-up) Fix perltidy
This follow-up fixes the formatting issue reported by xt/perltidy.t.

Test plan:
1. Run prove xt/perltidy.t
2. Confirm it passes.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:43 +00:00
Pedro Amorim 18469d46c4
Bug 42667: DBRev 26.06.00.027
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:42 +00:00
Lucas Gass 5694e63a8c
Bug 42667: (follow-up) Fix edit button inside of preview SQL modal
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:40 +00:00
Lucas Gass 3d7b5c5a95
Bug 42667: (follow-up) Update dropdown so users can only choose to edit own report
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:39 +00:00
Martin Renvoize 51d1fe9498
Bug 42667: (QA follow-up) Test edit permission on ownerless reports
Add two test cases covering reports with no borrowernumber (NULL owner):
a patron without edit_all_reports should be denied, one with the
permission should succeed. The POD documents this case but it was
not previously exercised.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:38 +00:00
Martin Renvoize 5da8ba1ce9
Bug 42667: (QA follow-up) Fix null guard and exception description
- Add null check around Koha::Patrons->find() in guided_reports.pl
  before calling ->has_permission, matching the defensive pattern
  already used in Koha::Report::check_edit_permission
- Add missing description to Koha::Exceptions::Report base class
  for consistency with its sub-exceptions

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:37 +00:00
Kyle M Hall 5b5c875df9
Bug 42667: Enforce permission in Koha::Report
Prevents possible template bugs from allowing editing
of reports by others without the proper permissions.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:36 +00:00
Kyle M Hall 1da4dcec00
Bug 42667: Hide edit controls as needed
Test plan:
1) Apply this patch set
2) Run updatedatabase.pl
3) Restart all the things!
4) Create 2 users with create_reports and not edit_all_reports
5) Create reports with each
6) Note that each cannot edit the others reports
7) Create a user with create_reports and edit_all_reports
8) Note the this user can edit reports from the previous two users

Edit button placements to look for:
* Reports table row
* Actions menu
* Toolbar

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:34 +00:00
Kyle M Hall df282a5f89
Bug 42667: Require edit_all_reports to edit other librarian's reports
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:33 +00:00
Kyle M Hall d2fa6156fd
Bug 42667: Add new edit_all_reports permission
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:56:29 +00:00
Jonathan Druart 08a4e352af
Bug 42330: (follow-up) Tidy
Something very messy happened during the last rebase before push.
Sorry!

I've restored the original version:
git rebase -i HEAD~258
Edit commit b02ca1270d and remove the next one (Tidy)
perl misc/devel/tidy.pl $(git diff --name-only HEAD~1)
git commit -a
git rebase --continue
Luckily there was no conflict!

I then picked the version of those files and here is the commit.

Use this to confirm:
`git show -w ad694a3411e677fcea96f315758793fd1fe0f203`
You should see changes that are not related to tidiness

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:11:34 +00:00
Martin Renvoize dfea147183
Bug 42385: (QA follow-up) Test PickupLibrary fall-through to ItemHomeLibrary
The original _GetCircControlBranch tests covered the case where CircControl
is PickupLibrary but no userenv branch is available, in which case the
resolution falls through to the ItemHomeLibrary behaviour. That coverage was
lost when the tests moved to t/db_dependent/Koha/Policy/Circulation.t, since
the PickupLibrary subtest always mocks a userenv.

This restores coverage for the fall-through path: with CircControl set to
PickupLibrary, no userenv, and no pickup_library_id, the controlling library
is resolved from the item's home library.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Koha/Policy/Circulation.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
(cherry picked from commit 6a13c6e5066384e59e52f0d48f5a430958671ef2)
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:11:33 +00:00
Tomás Cohen Arazi 275d870be7
Bug 42385: Remove _GetCircControlBranch
Removes the deprecated _GetCircControlBranch function and its export
from C4::Circulation. Removes the corresponding tests from
Circulation.t (now covered by t/db_dependent/Koha/Policy/Circulation.t).

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Circulation.t \
          t/db_dependent/Koha/Policy/Circulation.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
(cherry picked from commit 8e784622815a2db2ce6f558987d7c0b0951ceec0)
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>

# Conflicts:
#	C4/Circulation.pm
#	t/db_dependent/Circulation.t
2026-09-21 11:11:32 +00:00
Tomás Cohen Arazi 4f1b98160e
Bug 42385: Use Koha::Policy::Circulation everywhere
Replaces all calls to _GetCircControlBranch with
Koha::Policy::Circulation->circ_control_library in:
- C4::Circulation (10 call sites)
- Koha::Item (1 call site)
- Koha::Recall (1 call site)
- reserve/request.pl (1 call site)
- svc/letters/preview (2 call sites)

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Koha/Policy/Circulation.t \
          t/db_dependent/Circulation.t \
          t/db_dependent/Koha/Items.t \
          t/db_dependent/Koha/Recall.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:11:30 +00:00
Tomás Cohen Arazi 5c833281ed
Bug 42385: Add Koha::Policy::Circulation
Adds Koha::Policy::Circulation->circ_control_library($item, $patron)
class method that resolves which library controls circulation rules,
based on the CircControl and HomeOrHoldingBranch system preferences.

This follows the same pattern as Koha::Policy::Holds->holds_control_library.

An optional pickup_library_id parameter allows callers in API/daemon
contexts to pass the library explicitly instead of relying on userenv.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Koha/Policy/Circulation.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:11:29 +00:00
Martin Renvoize 255eeaf4f6
Bug 43375: (QA follow-up) Add test coverage for form-data fallback in userinfo parsing
_get_data_and_patron() falls back to Mojo::Parameters form-data
parsing whenever $tx->res->json fails to decode the userinfo
response body. That branch was untested: all existing userinfo_url
subtests mock json() to return a hashref, so the fallback path was
never actually exercised.

This adds a subtest that mocks a genuine url-encoded response body
(json() returning undef) and confirms the fallback still maps data
and finds the matching patron correctly.

Test plan:
1. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Auth/Client/OAuth.t
=> SUCCESS: All 6 subtests pass, including the new fallback-path test
2. Sign off

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:11:28 +00:00
Tomás Cohen Arazi 8b1e8f80ba
Bug 43375: Use Mojo json() with form-data fallback for userinfo parsing
This patch simplifies the userinfo response parsing by relying on
Mojo::Message::Response->json which decodes JSON regardless of
Content-Type header parameters. If JSON decoding fails (returns undef),
it falls back to URL-encoded form data parsing.

This fixes integration with APIs that return extra Content-Type
parameters (e.g. Microsoft Graph returns odata.metadata,
odata.streaming, and IEEE754Compatible alongside charset) which
previously caused the strict regex to fail and silently misparse
the response as form data.

Test plan:
1. Apply the regression test (previous patch)
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Auth/Client/OAuth.t
=> FAIL: Subtest fails - Content-Type with extra params not recognized
3. Apply this patch
4. Repeat step 2
=> SUCCESS: All tests pass
5. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:11:27 +00:00
Tomás Cohen Arazi 63ffc0b28f
Bug 43375: Regression tests
This patch adds a regression test demonstrating that the OAuth
userinfo_url response parsing fails when the Content-Type header
contains extra parameters beyond charset (e.g. Microsoft Graph API
returns application/json;odata.metadata=minimal;odata.streaming=true).

The strict regex in _get_data_and_patron only matches Content-Type
values with an optional ;charset=... suffix, causing valid JSON
responses to be silently misinterpreted as URL-encoded form data.

Test plan:
1. Apply this patch
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Auth/Client/OAuth.t
=> FAIL: New subtest fails - email is undef, patron not found
3. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:11:26 +00:00
Olivia Reynolds 6975b6616b
Bug 30319: Remove appropriate hidden fields on self-modification
Previously, the system preference
PatronSelfRegistrationBorrowerUnwantedField was used to remove hidden
fields regardless of whether the operation was self-registration or
self-modification. This patch uses the appropriate set of hidden fields
depending on the current operation (create/update).

Test plan:
1.  Set OPACPatronDetails to "Allow".
2.  Ensure PatronSelfModificationBorrowerUnwantedField is empty.
3.  Set PatronSelfRegistrationBorrowerUnwantedField to exclude
    Title (towards the bottom right of the modal).
4.  Log into the OPAC, and navigate to Your account > Personal details.
5.  Change your title and submit the request.
6.  Verify you see the notice "No changes were made." at the top of the
    page.
7.  Apply patch.
8.  Change your title again and submit the request.
9.  Verify the request is submitted successfully.
10. Return to the personal details screen, but don't submit.
11. Set PatronSelfModificationBorrowerUnwantedField to exclude Title.
12. On the preloaded personal details screen, update the title and
    submit.
13. Verify you see the notice "No changes were made." at the top of the
    page.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-21 11:11:22 +00:00
Baptiste Wojtkowski 0fc43dd5a3
Bug 41742: (follow-up) Add pod to AddBudgetPeriod
Create a pod coverage for AddBudgetPeriod

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-16 14:42:14 +00:00
Olivia Reynolds 0ae6c196ec
Bug 39337: (follow-up) Mock OpacSuppression in Output tests
Before the refactoring in this bug, redirect_if_opac_suppressed was
intended to be called after checking OpacSuppression was enabled. Now
it performs this check internally, so to adapt these unit tests
correspondingly, we mock OpacSuppression to be enabled, as was the
implied state before.

Test plan:
1. Prove t/db_dependent/Output.t and verify all tests pass.

Sponsored-by: Open Fifth
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-16 13:10:56 +00:00
Pedro Amorim bb05ef7ce8
Bug 42310: DBRev 26.06.00.026
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:18 +00:00
Pedro Amorim 1b903bc3c5
Bug 42310: (RM follow-up) Refresh stale DBIC checksum on Koha::Schema::Result::Branch
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:17 +00:00
Tomás Cohen Arazi 76d156d4fc
Bug 42310: (QA follow-up) Clean up stale references to dropped calendar tables
- cleanup_database.pl: rewrite DeleteSpecialHolidays to purge the new
  library_single_closures and library_closure_exceptions tables via their
  Koha::Objects classes (was querying the dropped special_holidays table).
- kohastructure.sql: fix the comment header above library_repeating_closures
  that still referred to repeatable_holidays.
- C4::Overdues: drop the dead GetSpecialHolidays / GetRepeatableHolidays /
  GetWdayFromItemnumber routines (unreferenced, exported nowhere) that still
  queried the dropped special_holidays / repeatable_holidays tables, along
  with the now-unused Date::Calc and Date::Manip imports.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:15 +00:00
Tomás Cohen Arazi 51fd136eb1
Bug 42310: (follow-up) Retire t/db_dependent/Holidays.t
t/db_dependent/Holidays.t was a legacy file whose coverage is now
provided by the two dedicated calendar test files. Its calendar
behaviour assertions (is_holiday for weekly/day-month/single holidays)
are already exercised by t/db_dependent/Calendar.t and
t/db_dependent/Koha/Library/Calendar.t. The remaining unique cases
queried the special_holidays / repeatable_holidays tables directly,
which this bug drops during the migration to the
Koha::Library::Calendar::*Closure schema, so the file could no longer
run.

This commit folds the unique coverage into
t/db_dependent/Koha/Library/Calendar.t, rewritten against the new
closure objects/API (no raw legacy-table SQL):

- is_holiday DST edge case (America/Santiago days_between)
- next_open_days / prev_open_days throwing NoOpenDays when the library
  is never open, and finding an open day created via an exception
- copy_to completeness: repeating-closure copy, exception copy, and
  exclusion of past-dated single closures and exceptions, plus the
  no-duplication guarantee for all four closure types

and then deletes t/db_dependent/Holidays.t.

Test plan:
- prove t/db_dependent/Koha/Library/Calendar.t
- prove t/db_dependent/Calendar.t

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:14 +00:00
Tomás Cohen Arazi 755521ba0f
Bug 42310: (follow-up) Make 16.12.00.032 upgrade self-contained
The historical 16.12.00.032 upgrade step (Bug 12063) instantiated
Koha::Calendar to push reserves.expirationdate forward by the max
pickup delay in open days. Depending on a live application class from a
frozen upgrade step is fragile: when this bug renamed and removed
Koha::Calendar, the migration had to be edited to chase the new
namespace.

Frozen updatedatabase.pl steps should be self-contained and prefer
direct DB access. Reimplement the holiday-aware date advance inline,
reading the legacy repeatable_holidays / special_holidays tables (which
still exist at this point in the upgrade path) directly, with no
dependency on any Koha::* calendar class. This keeps the step working
regardless of later refactors and removes the calendar-class reference
from updatedatabase.pl entirely.

The weekly / day-month / single-closure logic and exception (open
override) handling mirror the historical C4::Calendar semantics.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:13 +00:00
Tomás Cohen Arazi f4268951f0
Bug 42310: (follow-up) Fix POD coverage for Koha::Checkouts::_type
The koha-qa pod_coverage check flags 'sub _type has no pod coverage'
in Koha/Checkouts.pm. The POD block above the sub was headed '=head3
type' while the sub is named '_type', so the documentation was not
associated with the sub. Correct the heading to '=head3 _type' to
match, mirroring the adjacent object_class POD.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:11 +00:00
Martin Renvoize 134ce6aa7c
Bug 42310: (QA follow-up) Add test coverage for add_* cache invalidation, empty calendars, and leap-year repeating closures
Coverage gaps identified during QA review:
- add_single_closure/add_exception were only ever tested against a cold
  cache; nothing proved they invalidate a cache already warmed with the
  stale answer before the add.
- No test exercised a library with no closures at all.
- No test exercised a repeating (day/month) closure on 29 February across
  a leap and a non-leap year.

Atomicupdate migration coverage is intentionally left out of scope.

Test plan:
1) prove t/db_dependent/Koha/Library/Calendar.t t/db_dependent/Calendar.t
   t/db_dependent/Holidays.t t/db_dependent/HoldsQueue.t

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:10 +00:00
Martin Renvoize 3ef756f5a0
Bug 42310: (QA follow-up) Refresh in-process closure state on add/delete_weekly_closure and add/delete_repeating_closure
add_weekly_closure/delete_weekly_closure and add_repeating_closure/
delete_repeating_closure returned $self without refreshing
$self->{weekly_closed_days}/{day_month_closed_days}, so a calendar object
would keep reporting the pre-change state to is_holiday (and everything
built on it - days_forward, next_open_days, etc.) until it was
re-instantiated. This is distinct from the _holidays Koha::Caches entry,
which single/exception closures already flush correctly on store/delete.

Call _init to rebuild both structures from the database after each
add/delete, so the same object reflects the change immediately.

Test plan:
1) prove t/db_dependent/Koha/Library/Calendar.t t/db_dependent/Calendar.t
   t/db_dependent/Holidays.t t/db_dependent/HoldsQueue.t
   t/db_dependent/Holds/WaitingReserves.t t/db_dependent/Koha/CurbsidePickups.t
   t/db_dependent/Reserves/CancelExpiredReserves.t
   t/db_dependent/api/v1/libraries.t

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:09 +00:00
Martin Renvoize 004fe4de21
Bug 42310: (QA follow-up) Reject in-place date changes on SingleClosure/Exception
store() only flushes the _holidays cache on insert, on the assumption that
updates only ever touch title/description. That assumption held for every
current caller (ModSingleholiday/ModExceptionholiday only ever change those
two fields), but nothing enforced it - a future caller updating date in
place would silently desync the cache for up to its TTL, with no error.

Make the assumption explicit: reject an update that changes date, rather
than silently allowing it. Callers that need to move a closure/exception to
a different date must delete and re-add.

Test plan:
1) prove t/db_dependent/Koha/Library/Calendar.t t/db_dependent/Calendar.t
   t/db_dependent/Holidays.t t/db_dependent/HoldsQueue.t

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:08 +00:00
Martin Renvoize 1399f8af42
Bug 42310: (QA follow-up) Restore weekday validation on weekly closures
C4::Calendar::insert_week_day_holiday used to croak "Invalid weekday
$weekday" unless $weekday =~ m/^[0-6]$/. That guard was dropped when the
insert delegated to Koha::Library::Calendar::WeeklyClosure, and nothing
replaced it - an out-of-range or undef weekday now persists silently to
library_weekly_closures instead of failing at the API boundary.

Add the check to WeeklyClosure->store() rather than the legacy wrapper, so
every caller (C4::Calendar, Koha::Library::Calendar->add_weekly_closure,
and any future REST/admin caller) gets it, not just the old entry point.

Test plan:
1) prove t/db_dependent/Koha/Library/Calendar.t t/db_dependent/Calendar.t
   t/db_dependent/Holidays.t t/db_dependent/HoldsQueue.t
   t/db_dependent/Holds/WaitingReserves.t

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:06 +00:00
Martin Renvoize d0645cc8b1
Bug 42310: (QA follow-up) Restore in-memory holiday cache sync in C4::Calendar
insert_single_holiday and insert_exception_holiday delegate to
Koha::Library::Calendar::SingleClosure/Exception, but stopped updating
$self->{single_holidays}/{exception_holidays} on the C4::Calendar object
itself, unlike the sibling insert_week_day_holiday/insert_day_month_holiday
methods which still do. isHoliday() reads exactly those hashes, so a date
just inserted via insert_single_holiday/insert_exception_holiday would read
back as "not a holiday" for the rest of that object's lifetime - reachable
from tools/newHolidays.pl and tools/exceptionHolidays.pl, which loop over
several dates on one $calendar object, checking isHoliday() before each
insert to skip duplicates.

While adding a regression test, also found that _init() has the same class
of bug on the DB-load path: it builds the single/exception lookup keys from
`split(/-/, $row->{date})`, which keeps the zero-padding from the DATE
string (e.g. "06"), while isHoliday() numifies its lookup key (e.g. "6").
Any single/exception holiday with a single-digit month or day was therefore
invisible to isHoliday() even on a freshly reloaded C4::Calendar object.
Both call sites now numify year/month/day before building the hash key.

Test plan:
1) prove t/db_dependent/Holidays.t t/db_dependent/Calendar.t
   t/db_dependent/Koha/Library/Calendar.t t/db_dependent/HoldsQueue.t

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:05 +00:00
Martin Renvoize e56aff8dfc
Bug 42310: (follow-up) Update Holidays.t for normalized calendar tables
The normalization commit dropped `repeatable_holidays` and
`special_holidays` but t/db_dependent/Holidays.t still referenced them
directly via raw SQL, so the test blew up on the first
`DELETE FROM special_holidays` with "Table doesn't exist". The earlier
namespace follow-up touched this file but only renamed Koha::Calendar
references, missing the SQL.

Replace the legacy table references with the four new normalized tables:

  repeatable_holidays (weekday set)    -> library_weekly_closures
  repeatable_holidays (weekday NULL)   -> library_repeating_closures
  special_holidays (isexception=0)     -> library_single_closures
  special_holidays (isexception=1)     -> library_closure_exceptions

- Two fixture-cleanup DELETEs now wipe all four new tables.
- The copy_to_branch 'no duplicates' check, previously two raw-SQL
  queries against the old two tables, becomes four queries against the
  new tables, keyed on each table's natural unique key
  (library_id+weekday / library_id+day+month / library_id+date).
- Plan for the copy_to_branch subtest bumps from 8 to 10 to cover the
  two extra duplicate-check assertions.

Test plan:
1. prove t/db_dependent/Holidays.t
2. All 15 top-level tests pass, including the 10 subtest assertions in
   copy_to_branch.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:03 +00:00
Martin Renvoize 811d8bfd37
Bug 42310: (follow-up) Move Koha::Calendar tree under Koha::Library::Calendar
Koha::Calendar is, by construction, a per-library object — it croaks
without a branchcode argument and every method is branch-scoped. The
top-level namespace hides that. Move the whole tree under
Koha::Library::Calendar to match the actual semantics, the existing
/libraries/{library_id}/calendar REST routes, and the established
Koha::Library::* convention (Group, Hour, FloatLimit, OverDriveInfo).

This commit:
- renames the Koha::Calendar(::*) tree to Koha::Library::Calendar(::*)
- updates package declarations, POD and internal 'use' statements
  within the moved tree
- updates the four DBIC Schema Result overrides (koha_object_class /
  koha_objects_class) to the new namespace
- updates callers inside C4::* modules (Calendar, Circulation,
  Reserves, Overdues, HoldsQueue), Koha::{Patron,Hold,Checkouts,
  CurbsidePickup}, circ/returns.pl, the installer and cronjobs
- updates the 42310-owned tests (Calendar tests, HoldsQueue.t,
  Holds/WaitingReserves.t, Holidays.t, Koha/CurbsidePickups.t,
  Reserves/CancelExpiredReserves.t and the libraries.t CRUD block)

Koha::Library->calendar, the REST controllers and the 42311 tests
are updated in separate follow-up commits under their respective
bugs.

Test plan:
- prove t/db_dependent/Koha/Library/Calendar.t
- prove t/db_dependent/Calendar.t
- prove t/db_dependent/Holidays.t t/db_dependent/HoldsQueue.t
- prove t/db_dependent/Holds/WaitingReserves.t
- prove t/db_dependent/Reserves/CancelExpiredReserves.t

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:02 +00:00
Martin Renvoize 7a8a220842
Bug 42310: (QA follow-up) Add closed_dates_in_range to Koha::Calendar
Introduces a public Koha::Calendar->closed_dates_in_range($from_dt,
$to_dt) method that returns an arrayref of ISO date strings representing
every closed day in the inclusive range, honouring weekly, annual
repeating, single and exception (open-override) closures.

This replaces callers that were reaching into Koha::Calendar private
state (_holidays, weekly_closed_days, day_month_closed_days) to
reconstruct the same logic. A follow-up under Bug 42206 converts the
REST controller; further callers can be migrated incrementally.

Test plan:
1. prove t/db_dependent/Koha/Calendar.t
2. Verify the new 'closed_dates_in_range' subtest passes

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:14:01 +00:00
Martin Renvoize 5875ce4c75
Bug 42310: (QA follow-up) Report duplicate rows skipped during migration
The new calendar tables add UNIQUE(library_id, weekday),
UNIQUE(library_id, day, month) and UNIQUE(library_id, date) constraints
that did not exist on repeatable_holidays / special_holidays, so the
INSERT IGNORE migration can silently drop legacy duplicate rows.

This patch compares source vs destination row counts for each of the
four migrations and emits a say_warning naming the constraint and
pointing the operator at their pre-upgrade backup so titles or
descriptions can be recovered if needed. Success lines now include
the migrated row count. DROP statements for the legacy tables are
guarded with TableExists and reported via say_success for
consistency with the rest of the atomicupdate.

Test plan:
1) Apply the update on a database containing legacy duplicates and
   verify the warning is emitted with the correct count.
2) Apply on a database with no duplicates and verify no warning
   appears.
3) Re-run the update and verify it is idempotent (no warnings, no
   re-migration).

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:59 +00:00
Martin Renvoize 548903ba02
Bug 42310: (QA follow-up) Harden Koha::Calendar->copy_to
Wrap the body in schema->txn_do so a failure partway through does not
leave the target library partially populated. Flush the target's
_holidays cache after the copy, and document the behaviour in POD:
weekly and repeating closures are always copied, but single closures
and exceptions are filtered to today-or-later dates by design.

Test plan:
1) prove t/db_dependent/Koha/Calendar.t
2) Confirm existing copy_to tests still pass.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:58 +00:00
Martin Renvoize c3880a70e6
Bug 42310: (QA follow-up) Fire per-row delete hooks in Koha::Calendar delete_* methods
The four delete_*_closure / delete_exception methods in Koha::Calendar
were calling ResultSet->delete, which issues a single bulk SQL DELETE
and never calls the row-level delete() override. The cache-invalidation
hooks on Koha::Calendar::SingleClosure->delete and
Koha::Calendar::Exception->delete were therefore skipped when callers
went through $library->calendar->delete_single_closure(...) or
delete_exception(...), and the <library>_holidays cache kept reporting
deleted dates as closed until its TTL expired.

The underlying tables enforce UNIQUE constraints on (library_id, weekday),
(library_id, day, month) and (library_id, date), so each search can
match at most one row. Switch the four methods to ->search({...},
{ rows => 1 })->single followed by ->delete on the row, which both
makes the single-row expectation explicit and ensures the per-row
delete override runs.

A regression test is added to t/db_dependent/Koha/Calendar.t asserting
that the _holidays cache is cleared after delete_single_closure and
delete_exception.

Test plan:
1) prove t/db_dependent/Koha/Calendar.t
2) Confirm the new 'delete_*_closure clears _holidays cache' subtest
   passes.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:57 +00:00
Tomás Cohen Arazi d4e4f57072
Bug 42310: (follow-up) Bake cache invalidation into Koha::Object classes
When using the new Koha::Calendar::* classes for writing the CRUD API
endpoints I noticed that in my attempt to keep C4::Calendar similar to what it
was, I ended up with cache invalidation not always done in the right place.

The holidays cache (keyed by {branchcode}_holidays) stores dates and their
open/closed status, not titles or descriptions. This means only inserts and
deletes need to invalidate it — metadata updates (title/description) do not.

Following the pattern established by Koha::AuthorisedValue and Koha::ItemType,
I added store() and delete() overrides to Koha::Calendar::SingleClosure and
Koha::Calendar::Exception:

- store() flushes the cache only on insert (!in_storage), not on updates,
  since the cache doesn't store titles or descriptions.
- delete() always flushes, since removing a closure changes the cached set.

This allowed removing the now-redundant _clear_cache calls from
Koha::Calendar::add_single_closure, add_exception, delete_single_closure,
delete_exception, and from C4::Calendar::ModSingleholiday and
ModExceptionholiday.

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: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:55 +00:00
Tomás Cohen Arazi cb7968387a
Bug 42310: (follow-up) Add CRUD methods to Koha::Calendar, delegate C4::Calendar
Add to Koha::Calendar:
  add_weekly_closure, add_repeating_closure, add_single_closure,
  add_exception, delete_weekly_closure, delete_repeating_closure,
  delete_single_closure, delete_exception, copy_to, _clear_cache

C4::Calendar write methods now delegate to Koha::Calendar instead
of using raw DBI. No old table references remain in C4::Calendar.

Tests in t/db_dependent/Koha/Calendar.t cover all CRUD operations:
create, verify via is_holiday, delete, copy_to (including
idempotency).

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: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:54 +00:00
Tomás Cohen Arazi 4d23dff433
Bug 42310: (follow-up) Adapt Calendar tests to new schema
Replace all references to the dropped tables (RepeatableHoliday,
SpecialHoliday resultsets and raw DBI inserts) with Koha::Object
classes: Koha::Calendar::WeeklyClosure, RepeatingClosure,
SingleClosure, and Exception.

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: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:52 +00:00
Martin Renvoize 166e9831ea
Bug 42310: DBIC schema manual updates
Add koha_object_class/koha_objects_class overrides for the four new
Result classes backing the normalized calendar tables, below the
auto-generated marker.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:51 +00:00
Martin Renvoize c0349ad142
Bug 42310: DBIC schema update
Regenerated via dbic --force after the library_weekly_closures,
library_repeating_closures, library_single_closures, and
library_closure_exceptions tables replaced repeatable_holidays and
special_holidays.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:49 +00:00
Tomás Cohen Arazi ca487a18e0
Bug 42310: (follow-up) Add missing POD
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: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:48 +00:00
Tomás Cohen Arazi 0629de1e54
Bug 42310: Refactor Koha::Calendar to use Koha::Calendar::* classes
Replace raw DBI queries in _init() and _holidays() with
Koha::Calendar::WeeklyClosures, RepeatingClosures, SingleClosures,
and Exceptions searches.

The internal data structures remain identical (weekly_closed_days
array, day_month_closed_days hash, _holidays hash with YYYYMMDD
keys) so all consumers (is_holiday, addDuration, next_open_days)
continue to work unchanged.

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: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:46 +00:00
Tomás Cohen Arazi 402938f7cd
Bug 42310: Add Koha::Calendar::* Object classes
Four Koha::Object + Koha::Objects pairs following the Koha::City(ies)
pattern:

- Koha::Calendar::WeeklyClosure(s) -> library_weekly_closures
- Koha::Calendar::RepeatingClosure(s) -> library_repeating_closures
- Koha::Calendar::SingleClosure(s) -> library_single_closures
- Koha::Calendar::Exception(s) -> library_closure_exceptions

DBIC schema files to be generated via the 'dbic' alias in KTD
after the database migration runs.

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: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:45 +00:00
Tomás Cohen Arazi 5a3e86d0b6
Bug 42310: Normalize calendar tables
Replace the two overloaded tables (repeatable_holidays, special_holidays)
with four purpose-specific tables:

- library_weekly_closures: weekly closed days (e.g. every Sunday)
- library_repeating_closures: annual repeating closures (e.g. Dec 25)
- library_single_closures: one-off closures with a DATE column
- library_closure_exceptions: open overrides with a DATE column

All use library_id FK and follow current community standards. It points to API
usage in a short term so it follows the API naming conventions.

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: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 16:13:40 +00:00
Martin Renvoize 4ff58e553b
Bug 31391: (QA follow-up) Remove unused variables in ModReserve recall conversion
$due_interval and $due_date (and $recall itself) returned by
Koha::Recalls->add_recall were captured but never used in either
branch of the new rank => "recall" handling. Drop the unused
assignment - no behaviour change.

Test plan:
1) prove t/db_dependent/Holds.t t/db_dependent/Reserves.t - all pass

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:16 +00:00
Martin Renvoize 0b7d3b856a
Bug 31391: (QA follow-up) Add missing test coverage for hold-to-recall conversion
Two code paths introduced by this bug had no direct test coverage:

- C4::Reserves::ModReserve's title-level branch of the new
  `rank => "recall"` conversion (only the item-level branch was
  tested).
- The `hold_convert` bypass in Koha::Item::can_be_recalled, which
  skips the "patron already has a hold on this item" check so an
  existing hold can be converted to a recall for the same patron.

No production code changes here, coverage only.

Test plan:
1) prove t/db_dependent/Holds.t t/db_dependent/Koha/Item.t - all pass
2) Confirm the new tests actually exercise the code they claim to:
   reverting the `unless ( $params->{hold_convert} )` guard in
   Koha/Item.pm locally makes the new Item.t subtest fail, as
   expected.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:15 +00:00
Martin Renvoize cd30d78c0e
Bug 31391: (QA follow-up) Fix UseRecalls check in Transfers REST controller
UseRecalls was converted from a YesNo syspref to a Choice preference
("off"/"opac"/"staff"/"opac_and_staff") earlier in this bug, and every
call site was updated from a truthy check to `ne "off"` - except this
one. Since even the string "off" is truthy in Perl, the guard in
Koha::REST::V1::Transfers::cancel was silently defeated: cancelling a
transfer via the REST API always attempted to revert an in-transit
recall, even with recalls disabled.

Test plan:
1) prove t/db_dependent/api/v1/transfers.t - new subtest
   'cancel() - UseRecalls disabled' fails against the old code
   (the in-transit recall gets reverted even though UseRecalls is off)
2) Apply this patch
3) Same test now passes

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:14 +00:00
Martin Renvoize 85c37deb77
Bug 41898: (follow-up) Guard bookings.js against missing DOM when patron has no bookings
js/tables/bookings.js unconditionally called AdditionalFilters.init()
for 'filter-completed' and 'filter-cancelled' on every page load where
the script is included. patron-detail-tabs.inc only renders those
filter elements (and #bookings_table itself) when the patron has at
least one booking, past or present. For any patron with no booking
history, this produced two console.debug 'Element not found' messages
on moremember.pl and circ/circulation.pl.

Guard the ready handler on the presence of #bookings_table, mirroring
the existing pattern in holds.js.

Test plan:
1) Open moremember.pl for a patron with no bookings; confirm no
   AdditionalFilters console messages appear.
2) Open moremember.pl / circ/circulation.pl for a patron with
   bookings; confirm the Bookings tab, filters, and table still work
   as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:13 +00:00
Pedro Amorim d0a79a677c
Bug 41920: (RM follow-up) Remove dead resultset build in Koha::Reports::running
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:12 +00:00
Lucas Gass 8d9e7e641a
Bug 41920: Add unit tests
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:11 +00:00
Lucas Gass fd82dca909
Bug 41920: Remove code from reports/guided_reports.pl
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:10 +00:00
Lucas Gass ada73c317c
Bug 41920: Move total_running_reports_per_instance_limit to Koha/Reports.pm
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:09 +00:00
Kyle M Hall 7ba7bbab7d
Bug 41920: Limit number of concurrent reports that can be run simultaneously for an instance of Koha
As a continuation of bug 41919,  we should limit the total number of simultaneous reports that can be run at a time.

Test Plan:
1) Set total_running_reports_per_instance_limit to 2 in your koha-conf.xml
2) Create 3 long running reports like: SELECT COUNT(*) FROM items i1 JOIN items i2 JOIN items i3 JOIN items i4 JOIN items i5 JOIN items i6
3) Run each report in turn by opening a new tab, browsing to the reports
   module, and choosing to run that report
5) Note the first two continue to run, but the last one gives an error
   that you have too many reports running simultaneously!

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:08 +00:00
Thibaud Guillot 92cbbd59d5
Bug 32748: (QA follow-up) Fix duplicate AV when limited to several libraries at once
When an authorised value is limited to more than one library, the
no_limit query in GetAuthorisedValues joins authorised_values_branches
and returns one row per associated library, so the value was listed
several times in the item edition dropdown.

Group by the authorised value so each one is only returned once, and
aggregate the restricted flag with MIN() so the value stays
unrestricted as soon as one of its libraries matches the current one.

Sponsored-by: BibLibre
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:06 +00:00
Thibaud Guillot d7f1426d58
Bug 32748: (QA follow-up) Tidy test file
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:05 +00:00
Thibaud Guillot e2506598c8
Bug 32748: (QA follow-up) Fix branchcode filtering on itemtypes
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:04 +00:00
Thibaud Guillot 0794f75adc
Bug 32748: (QA follow-up) Fix cache key and add tests
Sponsored-by: BibLibre
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:03 +00:00
Lisette Scheer 5205fc8bcc
Bug 32748: (QA follow-up) Fix spelling and tidy
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:45:02 +00:00
Thibaud Guillot b443b31998
Bug 32748: Fix item edition when authorised values is restricted
When we set an authorised value linked to one library,
users who do not belong to this library do not get the
value and can therefore overwrite it.

This patch add current value to the dropdown list even if it comes from an authorized value of another library.

Test plan :

1) Edit an item with one of these fields has an authorized value from
another library.
2) On 'edit' you will see AV but with '(Not an authorised value)' string
   and a warn
3) Apply this patch
4) Refresh page and edit item again
5) Now if the authorised value belongs to another lib and is the current
   value, you will see it with a tooltip and a warn. But if there are
authorised values restricted to another lib and isn't current value,
normally it will be not present in the option list.

Sponsored-by: BibLibre

Signed-off-by: Mathieu Saby <mathsabypro@gmail.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-15 14:44:51 +00:00
Pedro Amorim f2aa1ecb53
Bug 31391: DBRev 26.06.00.025
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:16:04 +00:00
Pedro Amorim 8ccba18691
Bug 31391: (RM follow-up) Enforce recalls permission when converting holds to recalls
The previous follow-up only blanked the button's label via JS for
users without the recalls permission; the button still rendered,
was still clickable, and modrequest.pl's cud-convertall had no
permission check at all, so the conversion still succeeded.

Adds CAN_user_recalls to the button's template condition (matching
the existing "Move selected holds" button) so it doesn't render at
all without the permission, drops the now-redundant JS check in
holds.js, and adds a manage_recalls check in modrequest.pl itself.
checkauth()'s userid is the login username, not a borrowernumber, so
this uses C4::Auth::haspermission() directly rather than
Koha::Patrons->find().

Test plan:
1) Set a default recalls rule in admin/smart-rules.pl.
2) Set UseRecalls to 'staff' or 'opac_and_staff'.
3) Give a test patron 'catalogue' + 'reserveforothers' but not
   'recalls'.
4) Check out an item, place a hold on it for another patron.
5) As the test patron, go to that record's Holds tab, confirm
   'Convert selected to recall' doesn't appear.
6) Since the button is gone, confirm the server side is enforced too:
   post op=cud-convertall directly to modrequest.pl and confirm it no
   longer converts the hold.
7) Grant 'recalls' and confirm the button reappears and works.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:16:03 +00:00
Katrin Fischer 324b4d2fbe
Bug 31391: (QA follow-up) Hide "Convert selected to recall" for users with no recalls permission
This introduces a permission check on "recalls" for the link on the holds summary page.

1) Go to http://localhost:8081/cgi-bin/koha/admin/smart-rules.pl, and under the default rule, set:
- 'Recalls allowed (total)': 5
- 'Recalls per record': 3
- 'On shelf recalls allowed': if any unavailable
- 'Recall due date interval': 5
- 'Recall overdue fine amount': 5
- 'Recall pickup period': 2
2) Go to 'Administration' > 'Global system preferences' (http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=UseRecalls), set 'UseRecalls' to 'can be placed through the staff interface only'.
3) Go to Henry's patron account, borrowernumber 19, set him a password (http://localhost:8081/cgi-bin/koha/members/member-password.pl?member=19), then edit his permissions (http://localhost:8081/cgi-bin/koha/members/member-flags.pl?member=19) and give him only 'catalogue' and 'reserveforothers' (place holds), do not give him 'recalls'.
4) As a superlibrarian, check out item 39999000003154 (biblionumber 76) to borrowernumber 23, then place a hold on that same record for borrowernumber 49
5) Log out and log back in as Henry.
6) Go to http://localhost:8081/cgi-bin/koha/reserve/request.pl?biblionumber=76, tick the checkbox next to the hold, click 'Convert selected to recall', confirm in the modal.
7) The hold gets cancelled and a recall gets created, even though Henry has no recalls permission at all, proving the action isn't actually gated by it.
8) Apply patch, re-run test plan to 6) - The link to convert should no longer display.
9) Toggle the recalls permission for Henry, link to convert should now reappear.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:16:02 +00:00
Katrin Fischer 3df7914925
Bug 31391: (QA follow-up) Make sure pickup-library is kept when converting holds to recalls
The code was using the patron's home library instead of passing the pickup-location
of the original hold. This is a simple variable change in 2 places for record level and item
level holds.

A) Set 'UseRecalls' to 'staff' at http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=UseRecalls
B) At http://localhost:8081/cgi-bin/koha/admin/smart-rules.pl set the default rule: 'Recalls allowed' = 5, 'Recalls per record' = 3, 'On shelf recalls allowed' = 'if any unavailable' (not 'all').
C) Use biblionumber '437' (has just one item, barcode '39999000019186'), check that item out to 'Henry Acevedo' (Patron A).
D) Place a record-level hold on it for 'Johnny Bryan' (Patron B), who's from 'Midway' — but set the hold's pickup location to 'Centerville' instead.
E) On http://localhost:8081/cgi-bin/koha/reserve/request.pl?biblionumber=437, tick the hold, click 'Convert selected to recall', confirm.
F) Check the new recall's pickup location on http://localhost:8081/cgi-bin/koha/recalls/request.pl?biblionumber=437
- Expected: 'Centerville'
- Without the patch applied, it will be the patron's home library: Midway
- With the patch applied, the pickup-location of the hold is kept: Centerville.

G) Cancel the recall.
H) Repeat the test plan, but in step D) place an item level hold instead of a record level hold.

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:16:01 +00:00
Katrin Fischer e128051d09
Bug 31391: (QA follow-up) Fix check for UseRecalls for messaging preferences in OPAC
We are skipping the notice, if UseRecalls == "off".
This now matches the check in staff too.

A) Go to the KTD staff interface at http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=UseRecalls and set 'UseRecalls' to any non-'off' value, e.g. 'can be placed through the OPAC only'.
B) Log into the OPAC at http://localhost:8080/cgi-bin/koha/opac-user.pl as any patron.
C) Go to http://localhost:8080/cgi-bin/koha/opac-messaging.pl
D) Look for the recall-related notice row (item recalled / recall notices) — it's missing, even though recalls are enabled.
E) Now flip 'UseRecalls' back to 'off' in step 1's page.
F) Reload http://localhost:8080/cgi-bin/koha/opac-messaging.pl — the recall notice row now shows up, even though recalls are off.
G) Steps 4 and 6 are the reverse of what should happen — confirms the condition is inverted.

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:16:00 +00:00
Katrin Fischer a5e8fdccbd
Bug 31391: (QA follow-up) Adjust for changes made by bug 42443
This fixes a test to use 'opac' in transfers.t

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:59 +00:00
Katrin Fischer 77f3567b59
Bug 31391: (QA follow-up) Fix mocked value in Holds.t
The pref was mocked to '1' (the old value) in Holds.t
Test now uses 'opac_and_staff'

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:58 +00:00
Katrin Fischer 5850a591bd
Bug 31391: (QA follow-up) Fix Recall button visibility in holdings table
Fixes a problem with the template variables that made the recall button
show in some places when UseRecalls was set to off.

As the problem was in item-status.inc that is used in several pages,
a small fix goes far here.

Part 1: opac-detail.tt / opac-basket.tt (default config, useRecalls = 'off')
1) Confirm UseRecalls is at its default (off)
2) Check out item 39999000003154 to patron 'Henry Acevedo'
3) Log into the OPAC as a different patron (e.g. koha user)
4) As koha user, go to the biblio detail page for the record we just checked out:
http://localhost:8080/cgi-bin/koha/opac-detail.pl?biblionumber=76
5) Look at the item's status row: next to "Checked out", a "Recall" button is visible, even though UseRecalls is off.
6) Click it, lands on opac-recall.pl, shows a "Recalls have not been enabled. Please contact your library.", which confirms it's a dead-end button that never should have rendered.
7) Same thing reproduces in the basket: as koha user, add that biblio to your cart (Add to cart on the results/detail page), then click the cart icon on the top left. The same button erroneous will show.

Part 2: opac-course-details.tt — staff-only mode still leaks the OPAC link
1) Continuing from the previous instructions: Set UseRecalls to "can be placed through the staff interface only".
2) Add the same checked-out item (39999000003154) to a course reserves list — http://localhost:8081/cgi-bin/koha/course_reserves/course-details.pl?course_id=1
3) Click "Add reserves"
4) As koha user on the OPAC, view that course's page: http://localhost:8080/cgi-bin/koha/opac-course-details.pl?course_id=1
5) The "Recall" button shows here too, even though UseRecalls=staff should mean OPAC patrons can't place recalls at all.

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:57 +00:00
Aleisha Amohia ca977dcd78
Bug 31391: Allow cancel recall if UseRecalls enabled, remove unnecessary vars
This follow-up includes the following changes:
- only allow a recall cancellation via the OPAC if UseRecalls is enabled for the OPAC.
- uses singleBranchMode var passed by C4::Auth in staff interface biblio recalls
- drops unused attribute_type_codes var
- drops unnecessary items var and uses biblio.items in template

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:56 +00:00
Katrin Fischer 65b8a6129d
Bug 31391: Add "Place recall" button in detail page toolbar
Adds a new entry "Place recall" to the detail page's toolbar, visible when:

- UseRecalls is set to allow recalls in staff
- User has recalls permission

This matches the behavior for holds and bookings, so seems to increase
consistency.

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:55 +00:00
Aleisha Amohia 98c8add185
Bug 31391: Request recalls and convert reserves on staff interface
This enhancement adds the ability to place recalls via the staff
interface by changing the UseRecalls system preference to have choices which
describe where recalls can be placed. Recalls can either be placed via
the OPAC, or the staff interface, or both. To be set to OPAC would be
consistent with current 'on' behaviour. UseRecalls is turned off by default.

This enhancement also adds the ability to convert existing holds to
recalls easily via the staff interface. The hold will be cancelled
and a recall placed using the hold's information.

To test:
1) Update database and restart services
2) Set UseRecalls to OPAC and set the relevant recalls circulation rules
OPAC only by default
3) Check out Item A to Patron B.
4) Log into the OPAC as Patron A. Confirm you can place, view and cancel
recalls as normal.
5) In the staff interface, set UseRecalls to Staff interface only.
6) In the OPAC, confirm you can still view and cancel recalls, but can
no longer place recalls.
7) In the staff interface, search for Item A and go to the Holds tab.
Place a hold for Patron C.
8) Check the checkbox for your hold and confirm the counts in the buttons above the Holds table are incremented as expected.
9) Click the button to Convert selected holds to recalls, a modal should pop up. Confirm all of the information is correct.
10) Confirm the conversion and once the page refreshes, your hold should be correctly cancelled using the new RECALLED reason.
11) Go to the Recalls tab. Confirm your hold has been converted to a
recall, and details like the patron, expiration, and pickup location have
carried across to the recall.
12) On this Recalls page, use the patron search to find Patron D. Test
placing a record-level recall by choosing to recall the next available item.
13) When the page refreshes, confirm the recall was successfully placed
with the correct details stored.
14) Repeat steps 12 and 13 with Patron E, this time place an item-level
recall by choosing an item in the 'place a recall on a specific item'
table. When selecting one of these items, the 'recall next available
item' section should become disabled.
15) Set UseRecalls back to OPAC only. Go back to the record and
view the Recalls tab.
16) Confirm you cannot search for a patron to place a recall, but can
still view and cancel recalls. Confirm you also cannot convert holds
to recalls.
17) Set UseRecalls to both staff interface and OPAC. Confirm you
can place recalls on both interfaces.
18) On the staff interface, test placing recalls that your circulation
rules do not allow. For example, if 'recalls per record' is 1, ensure
you're blocked from placing a second recall on a record for a patron. Another example, testing converting a hold to a recall when there are NO checked out items, as recalls can only be placed if something is checked out.
19) Place a hold for one of the patron's that already has a recall.
Once complete, try to convert this hold into a recall. Confirm you
are blocked and shown a message that the hold cannot be converted to a
recall.
20) Confirm patron messaging preferences for recalls notices can still be configured as expected.
21) Ensure tests pass:

* t/db_dependent/Circulation.t
* t/db_dependent/Circulation/transferbook.t
* t/db_dependent/Holds.t
* t/db_dependent/Koha/Biblio.t
* t/db_dependent/Koha/Item.t
* t/db_dependent/SIP/Transaction.t
* t/db_dependent/Search.t
* t/db_dependent/XSLT.t

Sponsored-by: Auckland University of Technology
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:54 +00:00
Aleisha Amohia 8211dd8d0f
Bug 31391: Add UseRecalls to Hea
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:53 +00:00
Aleisha Amohia 401a52b833
Bug 31391: Implement UseRecalls choices on staff interface
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:52 +00:00
Aleisha Amohia b9b80b9471
Bug 31391: Implement UseRecalls choices in tests
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:51 +00:00
Aleisha Amohia 6091466f51
Bug 31391: Implement UseRecalls choices on OPAC
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:49 +00:00
Aleisha Amohia 0f35039974
Bug 31391: Implement UseRecalls choices in modules
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:48 +00:00
Aleisha Amohia f9c159e0a3
Bug 31391: Update the UseRecalls system preference to have choices
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:47 +00:00
Aleisha Amohia 0ebf8a632e
Bug 31391: Recalled authorised value for hold cancellation
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 14:15:42 +00:00
Lucas Gass d9d4c6c4fd
Bug 36868: (follow-up) Remove 2x call of holds.js, hold-group.js
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 11:42:46 +00:00
Lucas Gass 11012af369
Bug 36868: (follow-up) Remove double instanciated hold_table_settings
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 11:42:45 +00:00
Lucas Gass 3405bb206f
Bug 36868: (follow-up) fix failing tt_kohaTable_tidy.t test
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-14 11:42:38 +00:00
nina martinez d1b39adfc5
Bug 39890: Add search, menu and footer skip links to the OPAC.
Before starting the test plan, you have to enable the following system
preferences:
CurbsidePickup, useDischarge, OPACHoldsHistory,
ILLModule,EnhancedMessagingPreferencesOPAC, CookieConsent, OPACPrivacy,
opacreadinghistory, UseRecalls, OPACHoldRequests, RoutingSerials,
EnableOpacSearchHistory, virtualshelves,suggestion,
OPACViewOthersSuggestions, TagsEnabled, OpacTopissue,OPACVirtualCard.

Test plan:

A) Add skip link to menu

1. Apply the patch, you don't have to be logged in for this part.
2. Go to these different pages: search result page, record details page
   (try the different views), and Most popular page.
3. Try navigating with the keyboard, fix the focus at the top of the
   page
4. You should see a skip link in red, 'skip to menu' appear. After
   pressing enter, the focus should be on the menu.
5. Now go to these different pages: Purchase suggestions page, search
   history page, Lists page.
6. Repeat the third step and check if the skip link "Skip to menu" does
   not appear on these pages.
7. Now you have to log in to a patron account.
8. Go to your Summary page.
9. Repeat the third step with the different pages of the menu (Summary,
   Charges, Personal details, Consents, Tags, Change password, Search
   history, Checkout history, Privacy, Holds history, Recalls history,
   Purchase suggestions, Messaging, Lists, Ask for discharge,
   Interlibrary loan request, Curbside pickups, My virtual card).
10. You should see the "Skip to menu" link, after pressing enter, the
    focus should be on the menu.

B) Add a skip link to the search box

1. Try navigating with the keyboard, fix the focus on the top of the
   page.
2. Check if there is a skip link "Skip to the search" in red on each
   page of the OPAC, except on the advanced search page.

C) Add a skip link to the footer

1. Try navigating with the keyboard, fix the focus at the top of the
   OPAC  page.
2. Check if there is a skip link 'Skip to footer' after the skip link
   'Skip to main content' on each page of the OPAC.
3. Now, disable the system preferences (OPACReportProblem and
   KohaAdminEmailAddress, CookieConsent, OpacKohaUrl,
   OpacLangSelectorMode
   = 'both', or OpacLangSelectorMode = 'footer') to remove the OPAC
   footer.
4. Repeat step 2. Now, you should not have the skip link to the footer.

D)  If all the tests succeed, sign off !

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:06:06 +00:00
Pedro Amorim c6bf858253
Bug 34632: (QA follow-up) Use Koha::Uploader for patron import files
Large files were failing to enqueue/process because the whole CSV was
embedded in the job's JSON args, hitting DB/broker size limits. This
now uploads the file via Koha::Uploader first (same pattern as MARC
staging) and only passes a small uploaded_file_id through the job,
and fixes the enqueue-failure message to not claim nothing happened
when a job was in fact created.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:06:05 +00:00
Pedro Amorim 5ca53cf2bc
Bug 34632: Add patron_import background job display template
patron_import.inc provides the report/detail/js blocks consumed by the
background job viewer: a summary of counts when finished, a live progress
bar while running, and the full feedback and error analysis in the detail
section. background_jobs.tt gains a patron_import entry in the job type
filter dropdown.

Test plan:
1) Apply patches, restart plack.
2) Navigate to the patron import tool:
  <staff_url>/cgi-bin/koha/tools/import_borrowers.pl
3) Upload the provided sample CSV file and click 'Import patrons'.
4) Confirm the page shows "The patron import job has been enqueued!" with a link to view the job.
5) Follow the link to the background job. Confirm a progress bar is shown while the job is running.
6) Once finished, confirm the report shows the correct counts (imported, overwritten, already_in_db, invalid, total).
7) Confirm the imported patrons exist under 'Patrons'.
8) Re-import the same file with 'Overwrite cardnumber' unchecked. Confirm 'already_in_db' count is 5.
9) Re-import again with 'Overwrite cardnumber' checked. Confirm 'overwritten' count is 5.
10) Visit the background jobs admin and filter by type 'Import patrons':
  <staff_url>/cgi-bin/koha/admin/background_jobs.pl
11) Confirm the jobs appear in the list.
12) Run the tests:
  prove t/db_dependent/Koha/BackgroundJob/PatronImport.t

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:06:04 +00:00
Pedro Amorim 88644d26af
Bug 34632: Use PatronImport background job in import_borrowers
import_borrowers.pl now reads the uploaded file into memory and calls
Koha::BackgroundJob::PatronImport->enqueue() instead of running the import
synchronously. On success the template shows a confirmation with a link to
the background job viewer; on failure it shows an error alert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:06:03 +00:00
Pedro Amorim c8c24445af
Bug 34632: Add tests for Koha::BackgroundJob::PatronImport
Tests cover enqueue() (status, queue, job_size, job_type) and six
process() scenarios: happy path, import errors (with validation that
the 25-error cap has been removed), already_in_db and overwritten,
patron list creation, and cancelled job handling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:06:02 +00:00
Pedro Amorim ae60b6ebaa
Bug 34632: Add Koha::BackgroundJob::PatronImport
Introduces a new background job class for patron imports. enqueue() reads
the uploaded file content into the job_args and uses line count as job_size
for progress tracking. process() reconstitutes the file handle from the
stored content, delegates to Koha::Patrons::Import with step_callback wired
to $self->step, handles patron list creation, stores the import results, and
clears file_content from the stored data to free space.

Also registers patron_import in Koha::BackgroundJob::core_types_to_classes
so _derived_class can resolve the job correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:06:01 +00:00
Pedro Amorim 770e5ca6ec
Bug 34632: Add step_callback support to Koha::Patrons::Import
Adds an optional step_callback coderef parameter to import_patrons(). When
provided it is called after every row — whether the row succeeds, is skipped
as a duplicate, or is rejected as invalid — so callers can track progress.

Also removes the 25-error cap that was there to protect browser response
size; that constraint no longer applies when results are stored in a
background job's data field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:05:59 +00:00
Kyle M Hall 115d26ecae
Bug 42679: (QA follow-up) Add tests for missing permissions and non-overdue checkouts
The existing tests pass even if the endpoint counts all checkouts
instead of overdues only. This patch adds a checkout due in the future
and tests the count is unchanged. It also tests that a patron without
the borrowers flag gets a 403.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:05:58 +00:00
Kyle M Hall 14e4d6cb43
Bug 42679: (QA follow-up) Remove search tag that belongs to bug 42508
This tag describes the /search/patrons endpoints added by bug 42508.
Nothing in this bug uses it, and if this bug is pushed alone main would
carry a tag for endpoints that don't exist. Bug 42508 should add it.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:05:57 +00:00
Tomás Cohen Arazi f7caec076f
Bug 42679: Add GET /patrons/{patron_id}/overdues_count endpoint
Returns the count of overdue checkouts for a patron as a plain integer.
Computed live from the database (date_due < NOW()) to ensure accuracy,
as overdue status is time-based with no discrete event to trigger updates.

This endpoint is intended for async loading in the patron search table,
allowing the main search results to render immediately while overdues
are fetched separately per visible page.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/api/v1/patrons_overdues_count.t
=> SUCCESS: Tests pass!
3. Sign off :-D

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:05:56 +00:00
Laura_Escamilla 5d75b6fdad
Bug 39420: (QA follow-up) Replace tabs with spaces
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:05:55 +00:00
Samuel Sowanick 97235dea0d
Bug 39420: adjusted additem.tt breadcrumb to lead back to item
Bug 39420: removed old breadcrumb - Update additem.tt

Bug 39420: Removed old edit breadcrumb.

Bug 39420: Fixing syntax error in END tag

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 16:05:52 +00:00
Jonathan Druart 693c447736
Bug 41995: Show all entries
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:07:05 +00:00
Jonathan Druart fe8a9a5529
Bug 43330: Simplify tests
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:07:04 +00:00
Laura_Escamilla d5f522e526
Bug 43330: Add regression test for TagsExternalDictionary
To test:

1. Set the TagsExternalDictionary system preference to a non-empty value, for example:

       /usr/bin/ispell

2. Restart Plack if necessary.

3. Visit a staff interface page which loads C4::Tags, such as the staff home page or a bibliographic detail page.

4. Observe:

       Undefined subroutine &C4::Tags::check_install called at C4/Tags.pm line 44.
       BEGIN failed--compilation aborted at C4/Tags.pm line 53.
       Compilation failed in require.
       BEGIN failed--compilation aborted.

5. Clear TagsExternalDictionary and clear/update the system preference cache.

6. Apply the patch.

7. Set TagsExternalDictionary to an empty value.

8. Verify that you can access the home page and other pages.

9. Set TagsExternalDictionary to a valid non-empty path such as
	/usr/bin/ispell

10. Verify the staff home page, catalog detail page, and Tools page load without HTTP 500 errors.

11. Prove t/db_dependent/TagsExternalDictionary.t
12. Sign off and have a great day! :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:07:03 +00:00
Laura_Escamilla 2934511182
Bug 43330: Fix C4::Tags compile failure with TagsExternalDictionary
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:07:02 +00:00
David Nind 7d52823ac5
Bug 43432: Move ReportsExportLimit to reports category
Move the ReportsExportLimit system preference from the
staff interface category to the reports category, as
it relates only to the reports module.

1. Go to Administration > System preferences.
2. Search for the ReportsExportLimit system preference.
3. Note that it is in the Staff interface > Options category.
4. Apply the patch.
5. Repeat step 2.
6. Note that it is now in the Reports > Options category.
7. Note that the "Reports access" section is now in sentence case.

Signed-off-by: Bastien CHAUVET <bastien.chauvet@inlibro.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:07:01 +00:00
David Nind 87e8f605bb
Bug 43432: Update ReportsExportLimit system preference description
The current description uses "records". It actually controls the
number of rows (or results) exported, which may or may not be records.

Additional updates were also made to improve the description notes.

1. Go to Administration > System preferences.
2. Search for the ReportsExportLimit system preference.
3. Note the use of "records" in the description - results returned may or
   may not necessarily be records.
4. Apply the patch.
5. Refresh the page.
6. Note the updated description:
   - "records" changed to "rows"
   - changes to the notes:
     . NOTE changed to bold, consistent with other descriptions
     . now uses an unordered list
     . active voice used
     . removed note about requiring a positive vale
     . added a note about the "Open in DataTables" option
7. Sign off 8-).

Current description:
Download a maximum of _____ records when downloading from the
reports module.
NOTE: Only a positive value will enforce a limit. A specific
limit in the report overrides this setting.

Updated description:
Download a maximum of _____ rows when exporting report results.
NOTE:
- Using LIMIT in a report overrides this setting.
- It does not limit results when using 'Open in DataTables'
  export options.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:07:00 +00:00
Andreas Jonsson efb310cda0
Bug 43346: MarkIssueReturned with no userenv
Since it is not clear what checkin_library signifies when marking an
issue on a lost item as returned we might as well fallback on setting
it to the issuing library when there is no userenv.

Test plan

* Run the unit test t/db_dependent/Koha/Account/Line.t

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:06:59 +00:00
Andreas Jonsson 7dbc37e187
Bug 43346: Unit test for payment without userenv
MarkIssueReturned needlessly fails when called from a session with no
logged in user such as a script or anonymous callback.  A specific
application where this is a problem when completing a payment
transaction in a plugin for opac online payment wich may be done via
polling or callback.

This unit test verifies that a payment can be completed when paying a
fee for a lost item with MarkLostItemsAsReturned set to onpayment and
no userenv set.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:06:58 +00:00
Owen Leonard f8ef545ca4
Bug 38638: Offer non-priority option when placing multiple holds
This patch adds a checkbox for setting holds as non-priority when
placing multiple holds at once.

To test, apply the patch and make sure the DisplayMultiPlaceHold system
preference is enabed.

- Perform a catalog search in the staff client which will return
  multiple results.
- Check the checkboxes for 2 or more titles in the results and click
  "Place hold" in the toolbar.
- When confirming the holds, check the "Non priority hold" checkbox.
- Confirm that the holds are saved as non priority holds.
  - You can check by viewing the list of holds on a title. The "Details"
    column for that hold should say "Non priority hold."

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:06:56 +00:00
David Nind b8d1993582
Bug 43225: Fix punctuation for renewal confirmation message
A renewal confirmation message (when the AllowFineOverrideRenewing
system preference is set to "Allow") is missing a period and space
between the amount due and the second sentence.

It currently reads:
"The patron has a debt of [amount]Are you sure you want to renew checkout(s)?"

Test plan:
1. Set the AllowFineOverrideRenewing system preference to "Allow".
2. Check out an item to a patron (for example, 39999000011418 to Mary Burton).
3. Add a $110 manual charge to the patron's account:
   - [Patron account] > Accounting > Create manual invoice
   - Should now have a message on the patron's page:
     Charges: Patron has outstanding charges of 110.00. Make payment Pay all charges
4. Attempt to renew the checked out item from the checkouts section:
   - Check out > Checkouts > Show checkouts
   - Below the checkouts table, select "Override fine restrictions"
   - Click "Renew all"
5. There is a pop-up message with incorrect punctuation:
     The patron has a debt of 110.00Are you sure you want to renew checkout(s)?
6. Click the cancel option.
7. Repeat steps 4 to 6 from the details section (Details > Checkouts >
   Show checkouts) and note the same incorrect punctuation.
8. Apply the patch.
9. Repeat steps 4 to 7.
10. The pop-up message now reads:
      The patron has a debt of 110.00. Are you sure you want to renew checkout(s)?
11. Sign off.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:06:55 +00:00
David Cook 448419f918
Bug 43401: (QA follow-up) tweak rspack.config.js comment and fix Makefile.PL
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:06:54 +00:00
Paul Derscheid 8339307961
Bug 43401: (follow-up) Note the Makefile.PL coupling in rspack.config.js
- Add a file-level comment reminding that every shipped output.path needs a
  matching 'NONE' target-map entry and cp step in Makefile.PL

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:06:53 +00:00
Lucas Gass 55f621fd14
Bug 43401: Send compiled js vue for the OPAC to the right dir
The OPAC vue bundle (koha-tmpl/opac-tmpl/bootstrap/js/vue/dist) was not
handled like its intranet counterpart in Makefile.PL: exclude it from the
generic target map and copy it explicitly into blib in move_compiled_js.

Test plan:
1. In a KTD shell, from the koha checkout:
     perl Makefile.PL
   (accept the defaults, 'standard' install mode is fine)
2. make -n move_compiled_js | grep 'js/vue/dist'
   => without this patch only the intranet cp line is printed;
      with it there is also a line copying
      koha-tmpl/opac-tmpl/bootstrap/js/vue/dist into
      blib/OPAC_TMPL_DIR/bootstrap/js/vue/
3. make
4. ls blib/INTRANET_TMPL_DIR/prog/js/vue/dist blib/OPAC_TMPL_DIR/bootstrap/js/vue/dist
   => both directories exist and contain the compiled bundles
      (e.g. islands.esm.js); no vue/dist directory is left anywhere
      else under blib.
5. git clean -xdf blib pm_to_blib Makefile Makefile.old && git checkout -- . to
   clean up.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-11 15:06:50 +00:00
Jan Kissig 374295c25c
Bug 42296: auto tidy for follow-ups
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:43 +00:00
Jan Kissig 0602877c31
Bug 42296: (follow-up) Remove unused base_url on barcode-print
The JS variable base_url on barcode-print is never referenced. The remaining JS block did not contain TT interpolation, so data-tt-allowed can be removed.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:41 +00:00
Jan Kissig 667c8883e6
Bug 42296: (follow-up) Split JS block in auth.tt
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:40 +00:00
Jonathan Druart 4a4843e481
Bug 42296: Tidy all script tags - trivial occurrences
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch only adds 'data-tt-allowed="true"' when needed. The work to
move the JS variable to a separate script tag has been done already on
bug 41324.

Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:38 +00:00
Pedro Amorim 80abb65c62
Bug 41318: DBRev 26.06.00.024
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:36 +00:00
Katrin Fischer 5ae82ddd18
Bug 41318: (QA follow-up) Change Options to Examples in system preference description
While 'Options' might give the impression that the selection is
limited to the listed values, 'Examples' seems the better wording.

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:35 +00:00
Jonathan Druart 18134d8343
Bug 41318: Rename AmazonLocale to AmazonLocaleTld
To be more flexible we could allow the top-level domain instead of
having a predefined list.
However this patch still list the different available options (and adds
Brazilian) for easy selection.

Note that this system preference is not used to retrieve the image but
only to link to from the image. Contrary to what the reporter of the bug
report thought. And actually what I thought as well until I finished to
write the patch and started testing it!
Anyway now the patch is written, there should not be any changes in
behaviour. Most of the time the links are actually broken when not using
.com. So I think we either broke the whole feature at some point, or it
never really worked. On a separate bug we could eventually have a look
at fetching from the locale tld.

Test plan:
Turn on OPACAmazonCoverImages and AmazonCoverImages
http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=5
http://localhost:8080/cgi-bin/koha/opac-detail.pl?biblionumber=5
=> You see the PBP cover
click on it, it opens the lightbox, click "(see the original image)" at
the bottom
=> This is where the syspref AmazonLocale/AmazonLocaleTld acts

Modify the syspref's value and confirm that the link is modified and
point to the correct domain (leading to 404 or broken pages most of the time,
at least in my tests).

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:34 +00:00
Pedro Amorim 3b2a4f53ed
Bug 42344: DBRev 26.06.00.023
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:33 +00:00
Jonathan Druart 9d0f7d2f3c
Bug 42344: Remove CircSidebar
Test plan:
Run the updatedatabase script and notice that the sidebar is visible in
the circulation module but that you can hide/collapse it on demand.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:31 +00:00
Pedro Amorim 35004269a4
Bug 10190: DBRev 26.06.00.022
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:30 +00:00
Chloé Zermatten 68d3ba779b
Bug 10190: (QA follow-up): fix: only remove restrict if no restricting overdues found
Where a patron had multiple overdues that ought to cause debarment, it was found that removing one removed the restriction.

This is because has_restricting_overdues() was looking up the ItemType object tied to the overdue issue, then passing it down to Koha::CirculationRules->get_effective_rules(). get_effective_rules(), however, expects an itemtype code.
This resulted in false negatives: the patron was treated as having no restricting overdues left where they in fact did.

Amend this by calling effective_itemtype() instead.

Note on side effects: I think this also will activate the previously potentially dead dedup logic in has_restricting_overdues (eq had been used to compare obj instance to obj instance which had no effect (itemtype() runs a find() on each iteration), and is now used to compare itemtype code to itemtype code).

Test plan (based on Pedro's comment number 949):

0) Pick a test opac patron, if using Henry from above remove the permissions
1) http://localhost:8081/cgi-bin/koha/admin/categories.pl, edit the test patron's category, confirm 'Overdue notice required' is set to 'Yes'.
2) Set syspref AutoRemoveOverduesRestrictions to when_no_overdue_causing_debarment.
3) http://localhost:8081/cgi-bin/koha/admin/circulation_triggers.pl, 'Add new trigger'. Leave 'Library' as 'All libraries' and 'Patron category' as 'All categories', set 'Item type' to 'Books'. For 'Letter' pick ODUE, check 'Email' under 'Transport type(s)', set delay to 5 days, for 'Restricts checkouts' select 'Yes', 'Submit'.
4) Check out barcodes 39999000019186 and 39999000007558 to the test patron (confirm both are itemtype 'Books'). Backdate both due dates 6+ days overdue.
5) perl misc/cronjobs/overdue_notices.pl -n — restricts the patron.
6) Check the patron's 'Restrictions' tab: confirm restricted.
7) Return barcode 39999000019186 only, leave 39999000007558
8) Check the patron's 'Restrictions' tab again. Notice they are still restricted (39999000007558 is still overdue).
9) Return barcode 39999000007558 also.
10) Check the patron's 'Restrictions' tab again. Notice the restriction is now removed (a hard refresh may be needed) as no restricting overdues are left.

Assisted-by: Claude Opus 5 (Anthropic)
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:29 +00:00
Chloé Zermatten 99442e36b3
Bug 10190: (QA follow-up): fix: prevent delay-less rule sets
Since the decision was made that the add / edit trigger form should
never let a trigger be created or edited to not have a delay, it
follows that this must apply to the rule set reset flow as well.

Retain the warning message to the user, and also disable the
'Confirm reset' button if the reset would result in delay-less
sets.

The same applies to the trigger deletion functionality: if deleting
a trigger that is a default for all libraries would then leave any
libraries with one or more delay-less rule set, block the trigger
deletion (deactivate 'Confirm deletion') and inform the user. Here
as well we also still display the warning message.

Test plan:

A) Create one or more 'Default for all libraries' triggers.
B) Create one or more library-specific triggers.
C) For one or more of these, add itemtype and/or category specific overrides with not delay override.
D) For one or more of these, add itemtype and/or category specific overrides with a delay override.
E) Select 'Reset' on a rule set with overrides of type D) and notice that the 'Confirm reset' button is enabled.
F) Select 'Reset' on a rule set with overrides of type C) and notice that the 'Confirm reset' button is disabled,
    and that the warning message explains why reset is unavailable
G) Select 'Delete' on the highest 'Default for all libraries' trigger, and notice that, where one or more library-specific overrides are of type D), 'Confirm deletion' is disabled, whereas it is otherwise enabled.
H) Delete a trigger for which no override exists and confirm success.
I) Delete a trigger for which only type D) overrides exist and confirm success.
J) Reset a rule set for which only type D) overrides exist and confirm success.
K) Reset a rule set for which no override exists and confirm success.

Assisted-by: Claude Opus 5 (Anthropic)
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:28 +00:00
Chloé Zermatten b38d6d5201
Bug 10190: (QA follow-up): fix: keyboard delay delete behaviour
If the 'Delay' input field value is deleted using backspace or other,
block form submission. The same behaviour apply if the delete button
is used, as both clear out the value.

As triggers with no delay are not processed and would also cause
higher triggers to then also not be processed, we do not allow the
creation of triggers without a set delay.

Triggers when edited individually do not check for impact on any overrides
they might have. It is therefore safest not to allow submission of a trigger
where the user explicitly unset the value.

An explicit 'reset to fallback' feature is possible and would constitute a follow up enhancement.

Test plan (based on comment 872):

A) 'Administration' > 'Circulation triggers' > 'Add new trigger' for any library/category. Set a Delay, a Notice, and a transport type.
B) Select the Delay field's value and clear it via the 'X' clear button.
C) Notice 'Submit' is disabled.
D) Select the Delay field's value and clear it via the keyboard (Backspace/Delete). Click 'Submit'.
E) Notice 'Submit' is disabled.

Assisted-by: Claude Opus 5 (Anthropic)
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:27 +00:00
Chloé Zermatten e40291cb2c
Bug 10190: (QA follow-up): fix: missing rewrite rule in koha-httpd.conf
Looking at Bug 41167, this ought to fix the 404 error noted in comment 192 for package installs.

We do have a test asserting that RewriteRules must be carried over, so:

Test plan:
1. Apply patch
2. prove xt/check_apache_conf.t
3. Confirm it passes

That said, I don't think it is replicable in ktd - the copied file at /etc/koha/apache-shared-intranet.conf
will remain stale unless cp_debian_files.pl runs. The migration issue itself might be ktd-specific.

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:26 +00:00
Chloé Zermatten ec22f77b76
Bug 10190: (QA follow-up): fix: permissions
Important note: the following assumes manage_circ_triggers is intended
as a sibling to manage_circ_rules, and that of the two, only
manage_circ_triggers ought to grant access to client-side circulation
trigger configuration actions.

When manage_circ_triggers was introduced, it was not applied client-side,
resulting in the router and menu permission checks incorrectly expecting
manage_circ_rules. Amend that.

Additionally, while the API endpoint blocked a user with only
manage_circ_triggers from editing non-triggers circulation rules,
it did not block a user with only manage_circ_rules from editing
trigger circulation rules. Add a guard clause.

Test plan (based on comment 872):
A) Go to a patron's 'More' > 'Set permissions' tab. Check 'catalogue' (needed for basic staff login), and under circulation-rules sub-permissions, check only 'Manage overdue notice triggers' - leave 'Manage circulation rules' unchecked.
B) Log in as that patron, go to 'Administration' - 'Circulation triggers' link present (admin-home.tt now correctly checks CAN_user_parameters_manage_circ_triggers), 'Circulation and fine rules' is not present.
C) Visit http://localhost:8081/cgi-bin/koha/admin/circulation_triggers directly - you are granted access.
D) Confirm that you can create and edit circulation triggers.
E) Go to a patron's 'More' > 'Set permissions' tab. Check 'catalogue' (needed for basic staff login), and under circulation-rules sub-permissions, check only 'Manage circulation rules' - leave 'Manage overdue notice triggers' unchecked.
F) Log in as that patron, go to 'Administration' - no 'Circulation triggers' link present (as we require manage_circ_triggers), however 'Circulation and fine rules' is present.
G) Visit http://localhost:8081/cgi-bin/koha/admin/circulation_triggers directly - you see "Error: You do not have permission to access this page" and are prompted to log in as a different user (as we require manage_circ_triggers).
H) Go to http://localhost:8081/cgi-bin/koha/admin/smart-rules.pl, and confirm that you can edit circulation rules for your library (no regression).

Assisted-by: Claude Opus 5 (Anthropic)
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:25 +00:00
Chloé Zermatten a4be8b914e
Bug 10190: (QA follow-up): fix: itiva script
The following issues were identified and resolved:

- items.itemtype does not exist -> swap to items.itype
- leftover WHERE clause still referred to overduerules -> removed
- with the join on overduerules removed there is no need to GROUP BY anymore
- Koha::CirculationRules->all -> use next() instead

Test plan (as per comment 872):
A) In KTD, set 'TalkingTechItivaPhoneNotification' to 'On'.
B) Run perl misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl --type=OVERDUE -o /tmp/out.txt -v
C) No errors / the script does not die mid run.

Quick disclaimer: I am not overly familiar with that script:
while I did remove the causes behind the script dying mid run,
I won't have spotted issues with expected output / outcome.

Assisted-by: Claude Opus 5 (Anthropic)
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:24 +00:00
Nick Clemens 14e7814c15
Bug 10190: (QA follow-up) Fix spelling for QA tools
Other complaints are invalid:
ContextA spelling - this is a variable
Use of _("") - this is in regex for 'overdue_(' when checking rules
git manipulation - not ideal adding and removing in same set, but this is a long
   work, not asking for a squash here

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:23 +00:00
Nick Clemens a571b00a31
Bug 10190: (QA follow-up) Add new permission to tests
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:22 +00:00
Nick Clemens 8150c532fa
Bug 10190: (QA follow-up) Adjust error message to list both permissions
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:21 +00:00
Chloé Zermatten 2e3f9bd1ce
Bug 10190: (QA follow-up): fixes phase four
- Display new manage_circ_triggers permission (add to template)
- Ensure loading messages are informative to the user

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:20 +00:00
Chloe Zermatten 763dc46e31
Bug 10190: (follow-up): test: cypress tests update
New tests:
- regression: mtt cells render as .fallback (bold italics)
- bg-success-subtle highlight on completed steps in the add flow
- disabled-Delete states:
  - patron category filter set
  - item type filter set
  - default library, if any library has a trigger higher that default's highest
- redirect to admin home if not permitted to access the page
- in the edit/add stepper form
  - check row actions are enabled at the 'selectOrAdd' step
  - check row actions are disabled once add mode is entered
- check the action column is always available on the main page

Clean up / general updates:
- replace the /api/v1/** catch-all intercept with per-endpoint intercepts so @get-rules waits resolve reliably
- remove now-redundant cy.wait("@get-rules") calls
- drop unused mount / dayjs / dates imports
- ensure expected strings match UI where strings have changed

Assisted-by: Claude Opus 4.7 (Anthropic)

AI used to draft enhancements tests. Changes were reviewed, tweaked, and commited manually and gradually (with a pre-submission commit squash). Tests were run multiple times and adjusted iteratively to match code changes, cover regressions as those were found, and handle vue page load.

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:19 +00:00
Chloe Zermatten d6f596c514
Bug 10190: (QA follow-up) UI/UX improvements and fixes - phase three
Trigger deletion and rule set reset
- warn the user that resetting or deleting a given set will break sets using it as fallback.
- indicate which sets will be affected.
- display a preview of the post reset state of each affected rule set.
- preparation: allow TriggerTable to take a title parameter.
- refactor to remove 'has_rules' from the store's rule suffixes list so those match the DB.
- DRY: refactor to implement TriggerTable rather than use custom templates.
- select context stage handles narrower viewports
- prevent crash on missing rule set

TriggersTable: refactor: the <a> elements used as buttons are now button elements
- this is so they may be marked as disabled.

Main page - trigger actions (delete button display)
- disable the trigger deletion button.
  - if the context has been narrowed down ('delete' deletes all explicit rule set per trigger per library).
  - if the trigger belongs to default library AND one or more libraries have a trigger of a number bigger than the selected one.
  This is to protect trigger sequentiality from being broken for the relevant libraries.
  We also include an on hover message to explain the disabled button.

Add/ Edit trigger form
- clarify letters 'undo' button behaviour:
- swap the native vue select X for a fa-undo icon.
- display 'undo override and reset to fallback' on hover.
- only display the add/edit button at the selectOrAdd step.
- restore selected trigger yellow highlight (regression fix).
- display any temporarily saved data from prior step in a green 'success' container.
- only messages and data pulled from the DB for informative purposes display in blue containers.

Other
- redirect to correct URL (admin home) if store load fails
- mtt fallbacks display as such consistently (bold italics) - is a bugfix.
- catch errors on store initialisation
- remove whitespace duplicate
- ensure that mtt, if set, always display

Assisted-by: Claude Opus 4.7 (Anthropic)

AI used to help trace back potential bug sources, flag potential regressions, and draft enhancements. Changes were reviewed, tweaked, and commited manually and gradually (with a pre-submission commit squash).

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:18 +00:00
Victor Grousset/tuxayo 622f0a4742
Bug 10190: (QA follow-up): fix pod of CirculationRules.pm
We have nothing to do with it but since the patches change the file, it
trips the QA script.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:16 +00:00
Chloe Zermatten 37a9684669
Bug 10190: (QA follow-up): UI/UX bug fixes - phase two
Fix: handle exiting modal
 - prevent data reload when the user exits the modal
 - as preparation for the above: refactor to use one handler only
Fix: new trigger saves with delay defined
 - Identified an old refactor omission - we were still passing 'i' despite the from now relying on this.triggerNumber
Fix: return to correct trigger tab after save or reset
Fix: restore delete button and bold italic info
 - restore display, fixes a regression introduced by the previous UI/UX patch set
Fix: order dropdown options by description or name (not code)
Fix: circ rule resets
 - restore reset funtionality
  (mtt was still occasionally passed as a string, and would then get compared to an array. Ensure this is not the case.)
 - do not trigger the data refresh if the current ruleset did not get deleted
 - make sure triggerNumber is defined when navigation is caused by reset
 - add the notice code in brackets to letter names for display (eg. Overdue notice (ODUE))

Assisted-by: Claude Opus 4.6 (Anthropic)

AI used to help trace back potential bug sources and draft fixes. Changes were reviewed, tweaked, and commited manually and gradually (with a pre-submission commit squash)

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:15 +00:00
Chloe Zermatten 0fa640b387
Bug 10190: (QA follow-up) UI/UX improvements and fixes
- Refactor (prepation): Extract form sub-components for better maintainability
  - Template stucture improvements (eg. consistent fieldset use)
  - Ensure triggerNumber is always cast to integer from query params
  - Add/Edit trigger form UI/UX improvements/fixes:
      - Restore add/edit button functionality on trigger tables in the form
      - Update Context section title to dynamically change depending on whether editing or displaying
      - Display trigger context as informational when not in confirmContext mode
      - Hide required labels and validation on view-only context fields
      - Always display trigger context at the top of the form
      - Add renewal/autorenewal summary (renewals allowed, automatic renewal) to circulation rules summary panel
      - Sort notice letter dropdowns by name rather than code
      - Update Circulation rules summary title to be more descriptive
  - Scroll behaviour improvements:
      - Extract scrollToElementById to store with polling and null guard
      - Block page scroll when modal is open
      - Disambiguate duplicate trigger-table element ids (trigger-table-main
        vs trigger-table-form) to fix unpredictable scroll-to behaviour

Assisted-by: Claude Sonnet 4.6 (Anthropic)

Experimenting a little here - not quite certain where we draw the line for substantial use so I thought I would add a description in case it's useful, especially since the AI code guidelines are quite fresh:

AI was used primarily to help parse longer Vue templates (CirculationTriggersFormAdd.vue) and help isolate elements for extractions as well as highlight template formatting inconsistencies and propose improvements. The refactoring itself was achieved starting from AI-proposed changes which were then thoroughly re-worked manually towards the code changes submitted.
I also trialled using it (currently experimenting with this) for minor coding tasks where there is a clear precedent (for example, to add renewal/autorenewal information and do so in a way that follows exactly what we had written).
In those cases, each code change made by AI was the outcome of multiple iterations with direct feedback (which it did require a fair amount of) from me to ensure the output matched what I would have written myself.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:14 +00:00
Chloe Zermatten 8f6ff0a73d
Bug 10190: (follow-up): feat: indicate mtt is required
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:13 +00:00
Chloe Zermatten 6dcfe9f878
Bug 10190: (follow-up): fix: handle fallbacks in edit/add
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:12 +00:00
Chloe Zermatten b12d701494
Bug 10190: (follow-up): fix: reset rule info message
The information message showing in the reset modal
still referred to an earlier version of the UI.
Update it to correctly name the relevant filter.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:11 +00:00
Chloe Zermatten aa905b0801
Bug 10190: (follow-up): fix: mtt formating: handle unset
This is needed to handle creating new triggers
for libraries for which all earlier triggers are
defaults (do not exist in the DB , are not explicitly set)

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:10 +00:00
Martin Renvoize e4aecaa519
Bug 10190: Add manage_circ_triggers permission for notice trigger access
Replaces the broad manage_circ_rules permission requirement on the
circulation triggers page with a new dedicated manage_circ_triggers
sub-permission (under parameters, flag 3).

- Add manage_circ_triggers to userpermissions.sql
- Update atomicupdate to add the permission, grant it to existing
  edit_notice_status_triggers users, and remove the old permission
- Update admin/circulation_triggers.pl to require manage_circ_triggers
- Add manage_circ_triggers as an OR option on all circulation_rules
  API endpoints (GET/PUT /circulation_rules, /kinds, /config)
- Restrict manage_circ_triggers users in the API controller to
  overdue_* rule names only on write

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:09 +00:00
Martin Renvoize fabe480e52
Bug 10190: (follow-up) Add missing CSP nonce to inline script tag
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:08 +00:00
Chloe Zermatten f2951ab377
Bug 10190: (follow-up) mtt formatting
The Transport Type options when displaying in 'edit' mode were showing as
a comma separated string rather than as separate options (as they do when being set).
Ammends this so that they display as separate options consistently.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:07 +00:00
Martin Renvoize 4fdc9c76e1
Bug 10190: Fix false conflict error when editing specific trigger rules
When editing a specific rule (e.g. */Kids/Books) alongside an all/all/all
rule, the conflict check incorrectly reported a conflict. The effective=true
API response always returns context as */*/* (fallback), so ruleSetInDb.context
differed from currentRuleSet.context (which was overridden to the real context).

Fix by overriding ruleSetInDb.context before the hasConflict comparison,
consistent with the existing pattern in setCurrentRuleSet.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:06 +00:00
Martin Renvoize b760367e25
Bug 10190: Fix patron_categories permission format
The x-koha-authorization was using 'parameters: catalogue' which the
Koha auth system interprets as a 'catalogue' subpermission within the
'parameters' module (which doesn't exist), causing a 403 for any user.

The correct format for the top-level 'catalogue' permission is
'catalogue: "1"', consistent with all other read-only API endpoints.

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:05 +00:00
Martin Renvoize bc461cdde1
Bug 10190: Center tables in add modal
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:03 +00:00
Martin Renvoize 88b16e6445
Bug 10190: Enforce manage_circ_rules_from_any_libraries permission
- Add OR-type x-koha-authorization to /circulation_rules, /circulation_rules/kinds,
  and /circulation_rules/config endpoints so users with either
  circulate:circulate_remaining_permissions OR parameters:manage_circ_rules
  can access them
- Enforce manage_circ_rules_from_any_libraries in the set_rules controller:
  users without this permission may only set rules for their own library and
  cannot set default (*) rules
- Add explicit use C4::Auth qw(haspermission) import to CirculationRules.pm
- Lock the library selector in the Vue UI for users who lack
  manage_circ_rules_from_any_libraries, defaulting them to their own library
- Update set_rules() tests to cover the new permission restrictions

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:02 +00:00
Martin Renvoize 14fe8cb2f5
Bug 10190: (QA follow-up) Fix six issues reported during QA
1. Letter context: setFilteredLetters() was reading library_id from
   currentRuleSet (which has no top-level library_id property) instead
   of this.context.library_id, causing library-specific letter templates
   to never match.

2. False conflict on edit: conflict check fetched rule set from DB with
   effective=false (raw), but the in-memory snapshot was fetched with
   effective=true (with fallbacks). Comparing raw vs effective always
   triggered a conflict when a context inherited any value from a more
   general rule. Fixed by using effective=true for the DB fetch too.

3. Incomplete deletion: updateCircRuleSets() inside deleteRuleSet() was
   called without await, creating a race condition where the page refresh
   could complete before the deletion API call, leaving stale data visible.
   Also added a guard to skip rule sets with no explicit rules for the
   trigger being deleted, and simplified rulesForDeletion construction.

4. No rules displayed on initial load: the route watcher in
   CirculationTriggersList fired immediately before init() had loaded
   patron categories and item types, causing setAllExhaustiveEffectiveRuleSets()
   to produce an empty result. Fixed with a metaInitialized flag that the
   watcher waits for before proceeding.

5. DefaultToLoggedInLibraryOverdueTriggers syspref not respected: the
   default_view template variable was computed but never applied to the
   store's currentLibraryId. Fixed by passing it to init().

6. UI colors: info box background changed from #84c1f3 to #DAECFB (lighter,
   matching the circulation rules page). Error alert messages changed from
   alert-info (blue) to alert-warning (yellow) to match Koha conventions.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:01 +00:00
Martin Renvoize 8f6cee51ea
Bug 10190: Fix permission mismatch on /circulation_rules/config endpoint
The config endpoint was gated on parameters: manage_circ_rules, while the
actual GET/PUT /circulation_rules endpoints require circulate:
circulate_remaining_permissions. Any user who can read/write circulation
rules should also be able to load the config that drives the UI.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:25:00 +00:00
Martin Renvoize e41d6b54ca
Bug 10190: Patron categories fetch shouldn't require full manage permission
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:59 +00:00
Martin Renvoize c53a2ead10
Bug 10190: fix failing Permissions.t and overhaul CirculationTriggers Cypress spec
Permissions.t:
- Remove edit_notice_status_triggers from userpermissions.sql so fresh installs
  no longer seed this deprecated permission (existing installs are handled by
  the atomic update)
- Add a defensive DELETE in the test to cover DBs that pre-date the atomic update

CirculationTriggers_spec.ts:
- Fix getMockCirculationRule: the helper was returning the old {rule_name,
  rule_value, branchcode, ...} format; update to the current API format
  {context: {...}, overdue_N_delay: ..., ...}
- Remove assertions for UI elements that do not exist (success/error flash
  messages, "Existing triggers" heading, "rule set will be removed" text)
- Fix reset confirmation text to match actual component output
- Fix duplicate patron_category_id "ST" in mock patron categories
- Remove aspirational tests that checked non-implemented behaviour
- Add describe block for the no-default-triggers warning (no rules anywhere,
  rules only at specific libraries, library link switches the filter)
- Add regression test for NaN trigger number when adding first rule for a library

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:58 +00:00
Martin Renvoize f631df5afd
Bug 10190: Fix test count in overdues.t
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:57 +00:00
Martin Renvoize b80df78435
Bug 10190: warn when no default triggers exist and show libraries with rules
When viewing the default library context with no triggers defined, show
a warning message explaining defaults apply to all libraries. If any
libraries have specific triggers, list them as clickable links that
switch the filter to that library. If no rules exist anywhere, show a
prompt to add the first trigger.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:56 +00:00
Martin Renvoize 17c3aab6c4
Bug 10190: fix NaN trigger number when adding first rule for a library
When no circulation rules exist for a library, triggerCounts[library_id]
is undefined, causing undefined + 1 = NaN. Use nullish coalescing to
default to 0 so the first trigger correctly gets number 1.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:55 +00:00
Martin Renvoize 7413d2724e
Bug 10190: remove has_rules sentinel from circulation_rules table
has_rules was a purely frontend marker stored as a circulation rule to
signal that a context had explicitly-saved overdue trigger rules. The
backend never reads it; the information is fully derivable from whether
any of delay/notice/mtt/restrict are non-null for a given context.

Replace all has_rules DB reads/writes with a hasExplicitRulesForTrigger()
helper that checks the four real rule suffixes directly. has_rules remains
as a computed in-memory concept in the Vue store (derived in findEffectiveRule)
so templates require no changes.

Also adds an atomicupdate to delete existing has_rules rows from the DB,
and removes the has_rules insert from the bug_10190 migration script.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:54 +00:00
Martin Renvoize 591263d5ef
Bug 10190: three minor issues
- overdue_notices.pl: fix verbose sprintf - remove spurious \$triggered
  argument (CLI 0/1 flag had replaced delay value in output) and use
  the \$borr string that was computed but never included in the message
- overdue_notices.pl: remove %already_queued hash which was populated
  in _enact_trigger but never read; dead code since the refactor
- C4/Overdues.pm GetBranchcodesWithOverdueRules: guard the global-rule
  expansion with @branchcodes check so an empty result (no rules at all)
  correctly returns an empty list instead of all branches

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:53 +00:00
Martin Renvoize 8659f4e016
Bug 10190: correction to x-koha-authorization on /circulation_rules/config
The config endpoint is used exclusively by the Circulation Triggers
Vue app, which is gated on parameters/manage_circ_rules. Using
acquisition/'*' and erm/1 was inconsistent copy-paste from an
unrelated endpoint and would 403 circ-rules admins while granting
access to unrelated module users.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:52 +00:00
Martin Renvoize 228bf75845
Bug 10190: fix four more issues spotted in the overdues script
- Patron.pm has_restricting_overdues: sort ASC not DESC so the oldest
  (most overdue) item is checked per branch/itemtype; DESC caused the
  deduplication skip to discard the very items most likely to trigger
  a restriction
- overdue_notices.pl: fix --itemtypesout option name in GetOptions
  (was 'itemtypeouts', never matched the documented --itemtypesout flag)
- overdue_notices.pl: fix typo 'catagorycode' -> 'categorycode' in
  --borcatout search, which silently broke category exclusion
- circulation_triggers.pl: add flagsrequired => parameters/manage_circ_rules
  so the admin page enforces the correct permission; also add missing
  use Koha::Notice::Templates

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:51 +00:00
Martin Renvoize 6640e3e58a
Bug 10190: fix three regressions in overdue_notices.pl
- Store patron name/address fields in \$borrower_overdues so
  prepare_letter_for_printing has data for printed/CSV/text notices
- Fix email->print fallback: look up items under original MTT key,
  not the effective (fallback) key
- Set \$admin_email_address from \$library unconditionally in
  _enact_trigger; previously undef in non-patron_homelibrary mode,
  stripping the from_address on all queued notices

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:50 +00:00
Martin Renvoize d539637c5e
Bug 10190: rewrite atomicupdate with correct 1:1 rule migration
The previous migration had two fundamental errors:

1. It manufactured phantom empty rows for all patron categories that
   had no rule, which corrupted the "most frequent" calculation.

2. It created categorycode=NULL (all-categories) default rules for
   every branchcode seen, silently extending overdue notice coverage
   to patron categories that had no configured rule in the old system.
   The backend (overdue_notices.pl) does not consult has_rules, so
   these invented defaults would cause notices to be sent to patrons
   that were deliberately uncovered.

The rewrite migrates only explicit (branchcode, categorycode) pairs
that had a positive delay configured. No default rules are created.
branchcode='' maps to NULL as before. exit is replaced with return
to avoid killing subsequent atomicupdate scripts.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:49 +00:00
Chloe Zermatten 8c8160761d
Bug 10190: Deprecate permission
user_permissions rows do not need manual cleanup because the FK on
user_permissions(module_bit, code) references permissions(module_bit, code)
ON DELETE CASCADE, so deleting from permissions automatically removes
per-patron rows.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:48 +00:00
Chloe Zermatten 564f15ca59
Bug 10190: prevent duplicate ruleset load
- prevent the initial API call and subsequent exhaustive applied ruleset list generation logic from running twice

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:46 +00:00
Chloe Zermatten 3bb6f2e69c
Bug 10190: (QA follow-up) swap undef for all in update db output
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:45 +00:00
Chloe Zermatten a45a7aa8e1
Bug 10190: trigger add/edit form UI/UX enhancements
- scroll to the add/edit form on button click
    - prevent redundant full page reload upon the user swapping between adding and editing while the modal is open
    - ensure robust scroll behaviour, handling:
          - the URL being accessed directly
          - the edit button in the table in the main page being used to access the form directly
          - the stepper workflow being used
          - the user swapping between add and edit once already looking at the form
- dynamic display of 'Transport Type' consistency: only show if a 'Notice' has been selected
- if a notice has been selected, prevent form submission if transport type isn't set and display an alert on submission attempt

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:44 +00:00
Chloe Zermatten d3022cb950
Bug 10190: (QA follow-up) add missing filter
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:43 +00:00
Chloe Zermatten d508b600dd
Bug 10190: enhance trigger context display in modal
- improve modal scroll behaviour
- improve modal dimensions
- display contexts as a horizontal list to make better use of the available space
- fully contain dropdowns within the modal body

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:42 +00:00
Chloe Zermatten b8f8175539
Bug 10190: (QA follow-up) typo
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:41 +00:00
Chloe Zermatten 607ddb5a3f
Bug 10190: (QA follow-up) amend copyright statement
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:40 +00:00
Chloe Zermatten 53a2b75851
Bug 10190: (QA follow-up) add new line at end of MessageTransportType.pm
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:39 +00:00
Chloe Zermatten 9c6781302c
Bug 10190: (QA follow-up) fix unix permissions on circulation_triggers.pl
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:38 +00:00
Chloe Zermatten d176ba4d59
Bug 10190: (QA follow-up) fix unix permissions on bug_10190.pl
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:37 +00:00
Chloe Zermatten 298b4b6efc
Bug 10190: refactor to remove redundant try..catch
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:36 +00:00
Chloe Zermatten 7e70013450
Bug 10190: remove circ triggers link from tools menu
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:35 +00:00
Chloe Zermatten cf8230cdda
Bug 10190: fix permissions
- add a config API endpoint to Circulations Triggers so permissions can be fetched
- update the permissions in the Vue App so that manage_circ_rules is required
- update the permissions in the Circulation Rules API so that manage_circ_rules is required for PUT operations
- update the circulation-rules store to so that it is compatible with recently intorduced compostables
- implement a redirect into the route so that permission requirements are honoured

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:34 +00:00
Chloe Zermatten 839bc81e80
Bug 10190: (follow-up) Complete Vue.js rewrite of Circulation Triggers administration interface
This patch aims to resolve the following issues as reported by comment 193:

- missing atomic update for overde_X_has_rules (amended to account for comment 227)
- existing delays being overwritten by the client when a rule set is displayed
- inability to save circulation overdue rule sets for context for which circulations rules where already set
- address bug_10190.pl db script warnings (undef values were being concatenated with strings)
- add a guard clause to bug_101090.pl: check that the tables the script pulls the data from do still exist, otherwise exit.
- remove duplicate Letters options in the circulation triggers add / edit form

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:33 +00:00
Pedro Amorim 5dca36b1ce
Bug 10190: (QA follow-up): Fix removal of tables
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:32 +00:00
Martin Renvoize 1dc42db13b
Bug 10190: Comprehensive Cypress tests for Circulation Triggers UI
This patch significantly enhances the Cypress test coverage for the new
Circulation Triggers administration interface, expanding from basic
breadcrumb checks to comprehensive end-to-end testing.

Test Coverage:
- Initial page load and API error handling
- Context filtering (library, patron category, item type)
- View toggling between explicit and all applicable rules
- Tab navigation between multiple triggers
- Full CRUD operations (Create, Read, Update, Delete)
- Rule set reset functionality
- Letter template filtering by library context
- Form validation and constraint checking
- Loading states and empty states
- Error handling for all operations
- Browser navigation (back/forward buttons)
- Complex scenarios with inheritance hierarchies

New Features:
- Mock data helper functions for test fixtures
- Comprehensive API interception for all endpoints
- Tests for both success and error paths
- Placeholder value testing for fallback rules
- Increment/decrement button testing
- Multi-step form workflow validation
- Confirmation dialog testing for destructive operations

The tests follow Koha's Cypress testing patterns established in the
ERM module, using proper API mocking, clear test descriptions, and
organized test suites.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:31 +00:00
Martin Renvoize bb88db9be3
Bug 10190: Update itiva script to reflect move to circ_rules
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:30 +00:00
Martin Renvoize 8d5d976ff0
Bug 10190: Remove old overduerules interface
This patch updates the link from the tools homepage to open the new
circulation triggers administration page.

We then remove the old overduerules controller and template, and finally
we drop the no longer referenced
C4::Overdues::GetOverdueMessageTransportTypes method and associated unit
tests.

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:29 +00:00
Martin Renvoize 4eb521ba18
Bug 10190: Migrate MarkIssueReturned test to circulation rules
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:28 +00:00
Martin Renvoize f056486560
Bug 10190: Update has_restricting_overdues to refer to circulation rules
This patch updates the Koha::Patron->has_restriction_overdues method to
refer to the migrated overdue rules.

We also correct an oversight in the method prior to this patch that
meant that not all overdues were being correctly checked for a
restriction.. we were incorrectly filtering to only overdues at the
current checkin branch.

Finally, we also take account of the itemtype level addition here too.

NOTE: There weren't any unit tests for this Koha::Patron method.. we
should add some here!

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:26 +00:00
Chloé Zermatten d2c575a93d
Bug 10190: Complete Vue.js rewrite of Circulation Triggers administration interface
This patch introduces a modern Vue.js-based interface for managing circulation triggers (overdue notice/status triggers), replacing the legacy Tools-based interface with a more intuitive, feature-rich administration page.

New Features

Enhanced Visibility & Navigation
- Exhaustive effective rule lists showing all rules that apply to each context
- Toggle between "all exhaustive rules" and "explicitly set rules" views
- Advanced filtering by patron category and/or item type
- Clear display of fallback/default rules for each context

Rule Management Actions
- Delete triggers (last trigger in a sequence for any given library)
- Reset entire rule sets for a given context (with safeguards)
- Edit existing triggers with improved context awareness
- Add new triggers with intelligent delay constraints

Improved User Experience
- Multi-step form wizard with clear workflow progression
- Context-aware letter template filtering (only shows applicable templates)
- Placeholder values showing fallback rules when clearing inputs
- Dynamic min/max constraints for delay values based on neighboring triggers
- Real-time form validation and feedback
- Partial page reloads (only reload affected sections, not entire page)
- Specific loading messages for different operations

Technical Improvements
- Centralized Vuex store for state management
- Reusable CirculationTriggersForm component
- API clients for libraries, circulation rules, item types, and patron categories
- Consistent async/await pattern throughout
- Separation of repositories, utilities, and services
- Improved naming consistency and code maintainability
- "Dumb" TriggersTable component for better separation of concerns

Test Plan

Prerequisites
1. Log in to staff interface with appropriate permissions (tools_edit_notice_status_triggers)
2. Ensure you have test data: multiple libraries, patron categories, item types, and notice templates
3. Navigate to Administration > Circulation triggers (new interface)

Part 1: Basic Navigation & Interface
1. Verify the new "Circulation triggers" link appears in Administration home under the Circulation section
2. Access the new interface and confirm it loads successfully
3. Verify the default view shows either "All libraries" or your logged-in library based on DefaultToLoggedInLibraryOverdueTriggers system preference
4. Confirm the interface displays a list of triggers organized by context (library/patron category/item type combinations)

Part 2: Viewing and Filtering Rules
1. Toggle between "All exhaustive rules" and "Explicitly set rules" views
  - Verify "All exhaustive rules" shows inherited/fallback rules
  - Verify "Explicitly set rules" shows only rules you've specifically configured
2. Test the patron category filter:
  - Select a specific patron category
  - Confirm only rules for that category are displayed
3. Test the item type filter:
  - Select a specific item type
  - Confirm only rules for that item type are displayed
4. Test combined filters (patron category + item type simultaneously)
5. Clear filters and verify all rules display again

Part 3: Adding New Triggers
1. Click "Add trigger" button
2. Step 1 - Confirm Context:
  - Select Library (required)
  - Select Patron category (required)
  - Select Item type (required)
  - Click "Confirm context"
3. Step 2 - Review Existing Triggers:
  - Verify existing triggers for this context are displayed
  - Note the trigger numbers and delays
4. Step 3 - Configure New Trigger:
  - Enter a delay value (should be constrained by neighboring triggers)
  - Try entering an invalid delay (too low or too high) - verify validation
  - Set "Restricts checkouts" (Yes/No/Fallback to default)
  - Note the placeholder showing the fallback value
  - Select a letter template
  - Verify only templates for the selected library context appear
  - Set "Debarring" options if desired
  - Select "Charged notice fees" if applicable
5. Submit the form
6. Verify the new trigger appears in the list without a full page reload
7. Verify success message is displayed

Part 4: Editing Existing Triggers
1. Locate an existing trigger in the list
2. Click the edit icon/button
3. Verify the form pre-populates with existing values
4. Test delay modification:
  - Try changing the delay within valid bounds - should succeed
  - Try changing delay beyond neighboring triggers - should show validation error
  - Use the increment/decrement buttons to adjust delay
5. Test clearing values:
  - Clear a field (e.g., delay or restrict checkouts)
  - Verify placeholder shows the fallback/default value
  - Submit with cleared value - should save as null and inherit fallback
6. Change the letter template to a different valid template
7. Submit changes
8. Verify the trigger updates in the list without full page reload
9. Verify the changes persist after refreshing the page

Part 5: Deleting Triggers
1. Identify a trigger that is the last in its sequence
2. Click the delete button/icon
3. Verify a confirmation dialog appears
4. Confirm deletion
5. Verify the trigger is removed from the list without page reload
6. Verify the deletion persists after page refresh
7. Test delete constraints:
  - Try to delete a trigger that's not the last in sequence
  - Verify appropriate error/warning message

Part 6: Resetting Rule Sets
1. Locate a context with explicitly set rules (not just inherited)
2. Ensure this is not the only rule set for this trigger sequence
3. Click the "Reset" button for that context
4. Verify confirmation dialog explains what will be reset
5. Confirm the reset
6. Verify the explicit rules are removed and context now shows inherited rules
7. Verify changes persist after page refresh
8. Test reset constraints:
  - Try to reset when it would leave no rules for that trigger
  - Verify appropriate warning/prevention

Part 7: Context-Specific Behavior
1. Test library-specific letter filtering:
  - Create/edit a trigger for a specific library
  - Verify only letter templates with matching branchcode or default templates appear
  - Switch to a different library context
  - Verify the letter list updates accordingly
2. Test default rules:
  - View rules for "All libraries" / "All patron categories" / "All item types"
  - Verify these are labeled as "Default rule for [context]"
  - Confirm these serve as fallback for more specific rules
3. Test DefaultToLoggedInLibraryOverdueTriggers preference:
  - Set preference to "Yes"
  - Reload circulation triggers page
  - Verify it defaults to your logged-in library
  - Set preference to "No"
  - Verify it defaults to "All libraries"

Part 8: Form Workflow & Validation
1. Test required fields:
  - Try submitting add/edit form without selecting library - should fail
  - Try submitting without patron category - should fail
  - Try submitting without item type - should fail
2. Test stepper navigation:
  - Start adding a trigger
  - Verify you must confirm context before proceeding
  - Verify you see existing triggers before editing/adding
  - Verify trigger form only appears after context confirmation
3. Test cancellation:
  - Start adding/editing a trigger
  - Click Cancel
  - Verify you return to the list without changes
  - Verify no partial data was saved

Part 9: Loading States & Messages
1. On slow connections or with many rules:
  - Verify "Loading context..." appears while fetching context data
  - Verify "Loading rule set information..." appears while fetching rules
  - Verify section-specific loading messages appear during updates
2. After form submission:
  - Verify appropriate success/error messages display
  - Verify loading indicators during async operations

Part 10: Permissions
1. Log in as a user without tools_edit_notice_status_triggers permission
2. Verify the "Circulation triggers" link does not appear in Administration
3. Try accessing /cgi-bin/koha/admin/circulation_triggers directly
4. Verify access is denied

Part 11: Data Integrity & Persistence
1. Create a complex set of rules with multiple triggers across different contexts
2. Refresh the page and verify all rules persist
3. Restart the Koha services
4. Verify all rules still display correctly
5. Check the database circulation_rules table:
  - Verify rules are stored with correct rule_name pattern (overdue_1_delay, etc.)
  - Verify null values are properly stored for inheritance
  - Verify the overdue_X_has_rules rule kind is present

Part 12: Edge Cases
1. Test with no existing triggers - verify empty state message
2. Test with maximum trigger sequences (e.g., trigger 1, 2, 3)
3. Test rapid successive edits
4. Test with special characters in letter template names
5. Test browser back/forward buttons during form workflow

Part 13: Comparison with Legacy Interface
1. Access the old interface if still available (via Tools menu)
2. Create a trigger using the new interface
3. Verify it appears correctly in the old interface (if applicable)
4. Create a trigger using the old interface (if still available)
5. Verify it appears correctly in the new interface

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:25 +00:00
Matt Blenkinsop d199a559c0
Bug 10190: Let the circulation_rules endpoint accept query parameters
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:24 +00:00
Martin Renvoize 6ff8024158
Bug 10190: Rework logic of overdue_notices
This patch enhances the overdue_notices script to understand the newly
introduced itemtype level for overdue rules.

The original logic for digesting overdues in the overdue notices script
was flawed given the additional itemtype handling introduced in this
patchset and the way in which it handles effective fallbacks for rules.

This patch tidies the file and reworks the logic so that we fetch
overdues by patron category and branch and then iterate through those
comparing to the correct effective rules and combing them into digest
groups based on notice + trigger level.

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:23 +00:00
Martin Renvoize d4e951b3d8
Bug 10190: Update GetBranchcodesWithOverdueRules to use CirculationRules
This patch updates the C4::Overdues method
GetBranchecodesWithOverdueRules to refer to the new location of these
rules in the circulation rules table. We also update the unit tests to
move the mocked data into the right location.

We could perhaps migrate this method in Koha::CirculationRules at a
later date?

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:22 +00:00
Martin Renvoize 40478e1871
Bug 10190: Add support for incrementable circulation rules
This patch adds support for _X_ grouped circulation rules to both the
internal and external API's for handling circulation rules.

This allows us to group overdue_X_rulename rules by an 'X' integer so we
can have triggers incremented and grouped together.

We also add the new overdue_x_* rule definition here for overdue trigger
rules.

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:21 +00:00
Martin Renvoize ca8fc3d8e9
Bug 10190: Fix unit test to exclude _X_ rules
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:20 +00:00
Martin Renvoize 372c5b8c8e
Bug 10190: Remove OverdueRule Koha Objects
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:19 +00:00
Martin Renvoize 00896a2137
Bug 10190: DBIC Schema update
Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:18 +00:00
Martin Renvoize 7b9bd02726
Bug 10190: DB migration for overduerules
This patch adds the data migration for overduerules to circulation
rules.

We attempt to create the default rules based on most commonly occuring
rule values and then add overrides for all other rule values we find.

Sponsored-by: Glasgow Colleges Library Group <https://library.cityofglasgowcollege.ac.uk>
Signed-off-by: George Harkins <George.Harkins@cityofglasgowcollege.ac.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:16 +00:00
Pedro Amorim 0adb49bf2f
Bug 36868: DBRev 26.06.00.021
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:15 +00:00
Lucas Gass b369ba5d17
Bug 36868: Move array splice in OPAC js to after the -1 check
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:14 +00:00
Lucas Gass 5579974cd1
Bug 36868: Remove dead code in opac-user.pl
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:13 +00:00
Brendan Lawlor ae903a008d
Bug 36868: (QA follow-up) Fix deletion off by one error
When the biblionumber is not in the array -1 is returned.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:12 +00:00
Lucas Gass 9b5f75a79e
Bug 36868: (follow-up) Fix deletion when nothing is in the cart, and make DB update better
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:11 +00:00
Lucas Gass fac6a1adfe
Bug 36868: (follow-up) Fix variable names
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:10 +00:00
Lucas Gass d0e2ee1cac
Bug 36868: (follow-up) Remove unused variable in opac-reserve.pl
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:09 +00:00
Lucas Gass 1e8f39e7a6
Bug 36868: Add logic for OPAC
1. APPLY PATCH, updatedatabase, restart_all
2. Search for the new AutoDeleteFromCartWhenHoldPlaced system preference
3. Turn it on for both staff and OPAC
4. From the staff interface put some items in your cart.
5. Place a hold on all of them.
6. Once the hold(s) have been successfully done those items should no longer be in your cart.
7. Put more items into your staff cart, place a hold on some of them but not all.
8. Only the items you have placed a hold on should be removed.
9. Go to the OPAC and put some items in your cart.
10. Place a hold on all of them, those items should now be deleted from the cart.
11. Place more items into your OPAC cart, place a hold on some of them but not all.
12. Only the items you have placed a hold on should be removed.
13. Switch the AutoDeleteFromCartWhenHoldPlaced to only be for 'staff'.
14. Try steps 4-8 again.
15. Switch the AutoDeleteFromCartWhenHoldPlaced to only be for 'opac'.
16. Try steps 9 -12 again.

Signed-off-by: Sam Lau <samalau@gmail.com>

Signed-off-by: Sarah Fletcher <sfletcher@ncwlibraries.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:08 +00:00
Lucas Gass 42ca5a7b6a
Bug 36868: (follow-up) Fix staff interface code
Signed-off-by: Sam Lau <samalau@gmail.com>

Signed-off-by: Sarah Fletcher <sfletcher@ncwlibraries.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:07 +00:00
Lucas Gass 723258423c
Bug 36868: Add system preference AutoDeleteFromCartWhenHoldPlaced
Signed-off-by: Sam Lau <samalau@gmail.com>

Signed-off-by: Sarah Fletcher <sfletcher@ncwlibraries.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-10 14:24:03 +00:00
Lucas Gass 4d50205d4a
Bug 42722: Add noItemsOnBib error message
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:50:03 +00:00
Eric Phetteplace f87ee4aecb
Bug 42722: Improve invalidPickupBranch error message
Ensure you have the patch from Bug 41879 and follow its test plan.
You should see the message 'Target item or record has an invalid
pickup location. Either move the hold onto a valid item type or
select a valid pickup location.' when attempting an invalid hold.

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:50:01 +00:00
Owen Leonard 4c0424994c
Bug 42685: Capitalization corrections in some ILL files
This patch makes two capitalization corrections in files related to ILL:

"ISO18626 Messages" -> "ISO18626 messages"
"Requesting Agencies" -> "Requesting agencies"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:50:00 +00:00
Andrii Nugged d64d02761d
Bug 43344: Use the current source alias in filter_by_active
Bug 36018 qualified the quantity column with the default "me" alias to
avoid ambiguity when joined tables also have a quantity column.

DBIx::Class does not always use "me" for related resultsets. The
many-to-many item_orders relationship uses "ordernumber", so evaluating
item.orders.filter_by_active from the OPAC refers to a non-existent
me.quantity column.

Use current_source_alias to qualify quantity with the alias of the actual
resultset. Add regression coverage for filtering active orders through the
item relationship.

Test plan:
Before applying the patch:
1. Set OPACAcquisitionDetails to Show.
2. Set AcqCreateItem to create an item when placing an order.
3. Create a basket and add an order for a bibliographic record, including an
   item.
4. Close the basket so the order is active and outstanding.
5. Open the bibliographic record in the OPAC.
6. Confirm the item is not shown as On order and the log contains an Unknown
   column 'me.quantity' error.

Apply the patch.

After applying the patch:
1. Open the same bibliographic record in the OPAC again.
2. Confirm the item is shown as On order and the SQL error is gone.

Co-Authored-By: Loontik <loontik@nugged.team>
Signed-off-by: Olivia Reynolds <olivia.reynolds@openfifth.co.uk>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:49:59 +00:00
Slava Shishkin 0d80c7d440
Bug 43296: Support valid patron attribute codes in circulation stats
Circulation statistics only recognise numeric patron attribute codes,
while patron attribute type codes are not restricted to numbers.

Validate Row, Column and Filter_borrower_attributes against the available
patron attribute types. Use generated SQL aliases and pass attribute
codes as bind parameters.

Test plan:
1. In Administration > Patron attribute types, create attribute types with codes
   SHOW-BCODE, SHOW.BCODE, SHOW BCODE and SQL); DROP TABLE borrowers; --..
2. Go to Reports > Statistics wizards > Circulation.
3. Use each attribute as Row and Column with an ordinary field on the other axis.
4. Confirm the report fails or uses the wrong field.

Apply the patch.
5. Restart Plack.
6. Repeat steps 3-4.
7. Confirm the reports succeed and use the selected patron attribute.
8. Test the same attributes as filters.
9. Confirm ordinary Row, Column and Filter fields still work.

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:49:58 +00:00
Owen Leonard 6de2c57c27
Bug 43325: Remove event attribute from point of sale receipt window
This patch removes event attributes from the point of sale receipt
template in favor of adding the "close_window" class to the links. The
class has a globally-defined event handler for the close window
operation

To test, apply the patch and enable the EnablePointOfSale system
preference if necessary.

- If necessary, got to Administration -> Debit types and create at least
  one debit type which has the "Can be sold" checkbox checked.
- Go to the point of sale page and add at least one item to purchase.
- Fill out the "Collect payment" form and confirm.
- When the payment is received, click the "Print receipt button" in the
  dialog.
- On the receipt window which opens, click the "Close" button. The
  window should close.
- Open the receipt window again and change the "accountlines_id"
  parameter in the URL to an accountlines ID which doesn't exist
- The page should show an error message instead of a receipt. Test the
  "Close" button in this view.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:49:57 +00:00
Owen Leonard 685be2e643
Bug 43319: Remove event attribute from library_groups.tt
This patch updates the library groups administration page so that an
event attribute is not necessary. We don't need an onclick event handler
to submit the form--we can just use a submit button as the menu item.

To test, apply the patch and go to Administration -> Library groups.

- If necessary, create a new library group.
- From the list of groups, click an "Add library" menu button and then
  click one of the libraries which appear as menu items.
- The page should reload and the library you selected should now show up
  as being part of that library group.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:49:56 +00:00
Matt Blenkinsop ec1691b59d
Bug 43320: (QA follow-up) Add html filter and fix translatability
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:49:55 +00:00
Nick Clemens 8e663ae7bd
Bug 43320: OPAC adjustments
Similar test plan, but on the OPAC

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:49:53 +00:00
Nick Clemens 403288c5cd
Bug 43320: Don't quoted already quoted searches and add feedback
Bug 31213 added a quoted search when no results were found,
this patch builds on that to:
1 - Notify the user when their search is repeated quoted
2 - Not repeat a search when already quoted

To test:
 0 - Set system preferences
     RedirectToSoleResult to 'Don't redirect'
     IntranetAddMastheadLibraryPulldown to Show
     IntranetCatalogSearchPulldown to Show
 1 - Add the sample records to your catalog
 2 - Search for the quoted phrase "happy family big" in the 'title' index
 3 - Note you get a result
 4 - Check the record - the phrase is not found, but the record contains all the terms
 5 - Search for the unquoted phrase: 6:20 man
 6 - Confirm search is quoted and result found
 7 - Apply patch, restart all
 8 - Repeat 5
 9 - Confirm you are now told search was quoted and result found
10 - Repeat 2
11 - Confirm no result found, no notice of repeated search

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:49:52 +00:00
Nick Clemens b37d6989e5
Bug 43448: Remove meaningless conditional
We are in a conditional based on AllowHoldPolicyOverride being enabled,
a second conditional with an OR on the value will always be true

To test:
 1 - Do not apply patch
 2 - Disable AllowHoldPolicyOverride
 3 - Create a biblio B with an item I at library A.
 4 - Configure this library A to not be a pickup location
 5 - Add a "Default holds policy by item type" for item I type where "Hold
    pickup library match" is "item's home library"
 6 - Try to place a hold on biblio B
    You should not be able to place a hold because there is no valid
    pickup locations
 7 - Enable AllowHoldPolicyOverride
 8 - Try to place a hold on biblio B
    You should now see all valid pickup locations in a dropdown list
    (with an exclamation mark in front of each option) with none selected
    by default
 9 - Verify you can place a title-level hold and an item-level hold
10 - Apply patch, repeat test plan, verify nothing has changed

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 15:49:49 +00:00
Jacob O'Mara 6b0dcc83d9
Bug 39658: Schema: Add Koha object class mappings
Map PatronAccountLink to Koha::Patron::AccountLink(s).
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:44 +00:00
Pedro Amorim 50efc570df
Bug 39658: dbic
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:43 +00:00
Pedro Amorim f1b3ce6f30
Bug 39658: DBRev 26.06.00.020
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:42 +00:00
Jacob O'Mara 3e483cd61a
Bug 39658: (follow-up) block checkout when linked accounts over charge limit
When AllowFineOverride is disabled and NoIssuesChargeLinkedAccounts
is exceeded, hide the barcode field and show 'Cannot check out'.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:41 +00:00
Jacob O'Mara bfd2474a1a
Bug 39658: (follow-up) linked hold pickup all types
Linked account hold pickup check now applies to all hold
statuses (Reserved, Transferred, Processing), not just Waiting.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:39 +00:00
Jacob O'Mara 6d245a060e
Bug 39658: Add link_to_patron and linked_account_links methods
- link_to_patron($other_patron): Links patrons, handles group logic
- linked_account_links(): Returns all links in patron's group
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:38 +00:00
Jacob O'Mara 6b1e0a4fc9
Bug 39658: Add Koha::Exceptions::PatronAccountLink
Add typed exceptions for patron account linking:
- AlreadyLinked: patrons already in same group
- DifferentGroups: patrons in different groups
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:37 +00:00
Jacob O'Mara 77057b3628
Bug 39658: Tests: Add API tests for patron account links endpoints
Test GET/POST/DELETE /api/v1/patrons/{id}/account_links.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:36 +00:00
Jacob O'Mara 1cb693317e
Bug 39658: Tests: Add unit tests for Koha::Patron::AccountLink
Test linking, group management, and linked_accounts methods.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:35 +00:00
Jacob O'Mara f41cd57410
Bug 39658: Admin: Add linked account system preferences UI
Add prefs for EnablePatronAccountLinking and related settings.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:34 +00:00
Jacob O'Mara 23de8ce68e
Bug 39658: Patron detail UI: Add linked accounts section
Display linked patrons with Add/Leave link group buttons.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:33 +00:00
Jacob O'Mara fa8a860a49
Bug 39658: Patron detail: Pass linked accounts to template
Add linked_accounts and link_group data for display.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:32 +00:00
Jacob O'Mara 2b210d6da8
Bug 39658: Circulation UI: Add linked account fee/hold messages
Display warnings for combined debt and linked hold pickup.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:31 +00:00
Jacob O'Mara 81b62e4bdf
Bug 39658: Circulation: Add linked account hold pickup redirect
Enable linked patrons to pick up each other's holds when allowed.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:29 +00:00
Jacob O'Mara 454714a56e
Bug 39658: Circulation: Add linked account fee/hold checks
DEBT_LINKED_ACCOUNTS blocker and LINKED_ACCOUNT_HOLD_PICKUP alert.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:28 +00:00
Jacob O'Mara 194b5388a3
Bug 39658: API: Add REST endpoints for patron account links
Endpoints: GET/POST/DELETE /patrons/{id}/account_links
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:27 +00:00
Jacob O'Mara 6fa77edc48
Bug 39658: Add linked account methods to Koha::Patron
Methods: account_link, linked_accounts, all_linked_borrowernumbers,
linked_accounts_debt. Also adds charge limit checking.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:26 +00:00
Jacob O'Mara 3f83d79424
Bug 39658: Add Koha::Patron::AccountLink(s) objects
New objects to manage patron account links.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:25 +00:00
Jacob O'Mara 98ee7e3f0d
Bug 39658: DB: Add patron_account_links table and sysprefs
Adds table and 3 sysprefs for patron account linking feature.
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Kellie Stephens <kellie.stephens@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:24 +00:00
Martin Renvoize 9371504d98
Bug 30144: (QA follow-up) Only show servicing instructions when EDIFACT is available
The new servicing instructions UI on the "add order" form was showing
unconditionally, even when the EDIFACT system preference is disabled
or the vendor has no EDI account configured. Elsewhere in acquisitions
(e.g. acqui/basket.tt) EDI-only UI is gated on
Koha.Preference('EDIFACT') && ediaccount, so apply the same check here.

Test plan:
1. With EDIFACT disabled, or EDIFACT enabled but the vendor has no
   entry in vendor_edi_accounts, open Acquisitions > a basket > Add
   order (neworderempty.pl)
   => No "Servicing instructions" field is shown
2. Enable EDIFACT and add a vendor_edi_accounts row for the vendor
   => The "Servicing instructions" field appears and behaves as before

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:23 +00:00
Martin Renvoize b210605ef7
Bug 30144: (QA follow-up) Fix t/Ediorder.t for JSON servicing_instruction format
Bug 30144 changed Koha::Edifact::Order::gir_segments() to expect
servicing_instruction as a JSON-encoded array of instruction groups
(added by Bug 41709 while working with the older plain-string format
that predated Bug 30144's change was never adapted to match).

As a plain string no longer decodes as JSON, gir_segments() silently
skipped the servicing instruction elements, leaving the GIR split and
special-character tests comparing against undef.

Test plan:
1. prove t/Ediorder.t
   => 4 tests failed before this patch, all pass after
2. prove t/db_dependent/Koha/Edifact/Order.t
   => still passes

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-09 12:07:21 +00:00
Jonathan Druart 58cda28f6a
Bug 41996: Fix kohastructure.sql
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-07 10:46:28 +00:00
Jonathan Druart 3594c6f60c
Bug 16631: Fix kohastructure.sql
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-07 10:46:27 +00:00
Jonathan Druart f612fda1e9
Bug 41995: Add nonce
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:14 +00:00
Jonathan Druart 76504e598a
Bug 41995: Remove extra JS code
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:12 +00:00
Jonathan Druart dd536cae2e
Bug 41995: Fix conflict with bug 37762
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:11 +00:00
Jonathan Druart 061cc915ad
Bug 41995: Add autocomplete on the admin home page
Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:10 +00:00
Jonathan Druart c9cfa985b6
Bug 41995: Autocomplete for syspref search
This patch adds autocompletion to the syspref search in the header.

Test plan:
1. Go to the admin home page - /cgi-bin/koha/admin/admin-home.pl
2. In the header start typing the name of a syspref and notice that
   there is now autocompletion (you need at least 3 chars)
3. Select an entry and confirm that you are correctly redirected to the
   correct search result

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:09 +00:00
Pedro Amorim 52e6e3ad53
Bug 41728: (RM follow-up): Add AddReturn marks ISO18626 request as LoanCompleted
Regression test from the RM's QA review (attachment 204865, comment
104/105 on the bug): drives a real ISO18626 request through AddReturn
and confirms it gets marked LoanCompleted and receives a
supplyingAgencyMessage, rather than only checking that the $messages
hashref no longer contains a stray NotIssued key. This closes the gap
the "Remove leftover duplicate data-consistency block" follow-up left
- that fix restored the $iso18626_request assignment, but nothing
previously exercised the ISO18626 progress-reporting path end to end.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>

Test plan:
1. prove t/db_dependent/Circulation.t
=> SUCCESS: 91 tests pass.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:07 +00:00
Martin Renvoize e0d32e073a
Bug 41728: (QA follow-up) Add coverage for the Wrongbranch blocker
No test in the suite asserted on $messages->{Wrongbranch} content
despite this being one of the blockers AddReturn's rewired
availability check produces (AllowReturnToBranch enforcement, moved
into Koha::Item->can_be_returned_at / Koha::Item::Availability::Checkin
by this bug). Add coverage for it.

Test plan:
1. prove t/db_dependent/Circulation.t
=> SUCCESS: 88 tests pass.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:06 +00:00
Martin Renvoize 8bcd634fb9
Bug 41728: (QA follow-up) Remove leftover duplicate data-consistency block
d43945e7279 moved the issued/patron data-consistency check earlier in
AddReturn (before blocker handling), as its commit message describes,
but only copied the block instead of deleting the original. The
leftover block ran unconditionally afterwards: since $patron is
already guaranteed set whenever $issue is true by that point, its
"else" branch fired on every normal checkin of an already-issued item,
incorrectly setting $messages->{NotIssued} and clearing item.onloan.
This corrupted the not_issued message surfaced to circ/returns.pl, SIP,
and the checkin audit record on every ordinary return.

The same leftover block also buried $iso18626_request's assignment
after a die (making it dead code), so ISO18626 progress-reporting on
checkin was never triggered. Restore the assignment at the point
$issue/$patron are extracted from the availability context, where it
belongs.

Test plan:
1. Return an item that is currently checked out.
2. Confirm the returned $messages hashref no longer contains a
   NotIssued key, and that item.onloan is only cleared for items that
   were genuinely not checked out.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:05 +00:00
Tomás Cohen Arazi ce9ffcca52
Bug 41728: (follow-up) Replace require with use for Availability::Checkin
There is no circular dependency, so the runtime require can be
replaced with a compile-time use statement.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:03 +00:00
Martin Renvoize fbe7c7a60f
Bug 41728: (QA follow-up) Move Item::Checkin::Availability to Item::Availability::Checkin
Introduce a dedicated Koha::Item::Availability::* namespace for checkers
that return a Koha::Result::Availability. Keeping these under the Item
subject prefix keeps them co-located with the Item they describe, while
the Availability sub-namespace gives future parallel checkers
(Checkout, Hold, Book, ...) an obvious home rather than scattering them
as single-file directories alongside Koha/Item/Transfer/ etc.

The $item->checkin_availability convenience method on Koha::Item is
preserved; only its internal delegation target changes.

Renames:
  Koha::Item::Checkin::Availability -> Koha::Item::Availability::Checkin
  Koha/Item/Checkin/Availability.pm -> Koha/Item/Availability/Checkin.pm
  t/db_dependent/Koha/Item/Checkin/Availability.t
    -> t/db_dependent/Koha/Item/Availability/Checkin.t

Test plan:
  prove t/db_dependent/Koha/Item/Availability/Checkin.t
  prove t/db_dependent/Circulation/Returns.t

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:02 +00:00
Martin Renvoize be74523028
Bug 41728: (QA follow-up) Move Availability::Result to Result::Availability
Consolidate result base classes under the Koha::Result::* namespace
alongside the existing Koha::Result::Boolean. This keeps return-value
wrappers discoverable in one place and avoids a lone Koha::Availability::
namespace that would otherwise host a single base class.

Renames:
  Koha::Availability::Result -> Koha::Result::Availability
  Koha/Availability/Result.pm -> Koha/Result/Availability.pm
  t/Koha/Availability/Result.t -> t/Koha/Result/Availability.t

Call sites in Koha::Item, Koha::Item::Checkin::Availability, and the
test files are updated accordingly.

Test plan:
  prove t/Koha/Result/Availability.t
  prove t/db_dependent/Koha/Item/Checkin/Availability.t

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:24:01 +00:00
Tomás Cohen Arazi dece71cb3e
Bug 41728: (follow-up) Add no_short_circuit option to checkin availability
This follow-up replaces the unconditional removal of short-circuit logic
with a caller-controlled no_short_circuit parameter, following the same
pattern established by Koha::Patron->can_place_holds.

By default, check() short-circuits on the first blocker. This is the
correct behavior for AddReturn, which processes blockers sequentially
and only acts on the first one it finds (BlockedWithdrawn => early return,
Wrongbranch => early return, BlockedLost => sets doreturn=0). Running all
policy checks only to discard the results is wasteful, specially when
some of them (e.g. can_be_returned_at) hit the database.

When no_short_circuit => 1 is passed, all checks run and all blockers
are collected. This is the behavior API consumers will need: present the
full set of issues in a single response instead of requiring
fix-one-recheck cycles.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Item/Checkin/Availability.t
=> SUCCESS: Tests pass!
  k$ prove t/db_dependent/Circul*
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:59 +00:00
Martin Renvoize 84bcca727a
Bug 41728: (QA follow-up) Collect all blockers instead of short-circuiting
Remove early returns from Koha::Item::Checkin::Availability::check() so
that all blockers, confirmations, and warnings are collected in a single
pass. Previously, the first blocker triggered an early return, meaning
callers could only discover one problem at a time.

This is particularly useful for API consumers who want to present the
full set of issues to the user in a single response rather than
requiring fix-one-recheck cycles.

The change is safe for AddReturn because it already handles blocker
priority through its sequential if/return structure:
  1. BlockedWithdrawn → early return before status updates
  2. Wrongbranch → early return after status updates
  3. BlockedLost → sets doreturn=0

Even when multiple blockers are present, AddReturn processes them in
the correct priority order.

Also makes existing tests deterministic by explicitly setting withdrawn
and itemlost values on test items, preventing random TestBuilder values
from triggering unrelated blockers now that all checks always run.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:58 +00:00
Martin Renvoize 8391917bab
Bug 41728: (QA follow-up) Restore withdrawn warning and use from availability
Restore the withdrawn warning in Koha::Item::Checkin::Availability that
was incorrectly removed. This warning is set when an item is withdrawn
but BlockReturnOfWithdrawnItems is off, making it useful for API
consumers in subsequent work.

Update AddReturn to read the withdrawn state from the availability
result's warnings rather than re-checking $item->withdrawn directly,
avoiding duplication of logic.

Add test for the withdrawn warning (non-blocked) path.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:57 +00:00
Martin Renvoize 24854abf30
Bug 41728: (QA follow-up) Move return policy logic to Koha::Item
Move the AllowReturnToBranch and branch transfer limit checks from both
C4::Circulation::CanBookBeReturned and
Koha::Item::Checkin::Availability::_check_return_policy into a single
authoritative method on Koha::Item: can_be_returned_at().

This eliminates the duplicate implementations and follows the Koha
convention of placing item-related logic on the Koha::Item object.

Changes:
- Add Koha::Item->can_be_returned_at() with AllowReturnToBranch policy
  and transfer limit checks
- Make CanBookBeReturned a thin wrapper delegating to the new method
- Update Checkin::Availability to call $item->can_be_returned_at()
- Remove _check_return_policy and unused withdrawn warning from
  the Availability class

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:56 +00:00
Martin Renvoize b689dd07cc
Bug 41728: (QA follow-up) Restore original execution order for Wrongbranch
The refactoring changed the relative order of the Wrongbranch early
return and several status-update operations. In the original AddReturn,
the execution order was:

  1. checkout/patron lookup
  2. not-issued handling + RecordLocalUseOnReturn
  3. withdrawn check (early return)
  4. location_update_trigger
  5. UpdateNotForLoanStatusOnCheckin
  6. CanBookBeReturned / Wrongbranch check (early return)
  7. BlockReturnOfLostItems

After the refactoring, the Wrongbranch blocker returned early before
steps 2, 4 and 5, causing three regressions:

  - RecordLocalUseOnReturn was not recorded for non-issued items
    scanned at the wrong branch
  - location_update_trigger was skipped for Wrongbranch returns
  - UpdateNotForLoanStatusOnCheckin was skipped for Wrongbranch returns

Fix by splitting blocker handling into two phases:

  1. BlockedWithdrawn: earliest early return, before status updates
     (matches original withdrawn check position)
  2. Wrongbranch and BlockedLost: after RecordLocalUseOnReturn,
     location_update_trigger, and UpdateNotForLoanStatusOnCheckin
     (matches original CanBookBeReturned position)

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:54 +00:00
Martin Renvoize d46b0c1106
Bug 41728: (QA follow-up) Move data consistency check before blocker handling
The data inconsistency die (checkout exists but patron does not) was
placed after the blocker handling block. This meant that if an item had
corrupt data AND triggered a blocker (e.g. withdrawn+blocked), the
blocker handler would return early with patron as undef instead of dying
on the inconsistency, silently masking database corruption.

Move the check to immediately after extracting context from the
availability result, before any blocker handling, matching the original
AddReturn behavior where the die fired during checkout lookup.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:53 +00:00
Kyle M Hall 2e4459ea69
Bug 41728: (QA follow-up) Declare issue and patron variables together
There seems to be no reason to declare $patron near the start of the sub
while $issue is declared where they are both populated.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:52 +00:00
Kyle M Hall dad9fd4a30
Bug 41728: (QA follow-up) Fix test accessing wrong key for checkout
The test was checking $result->{issue} which doesn't exist on the
Result object. The correct accessor is $result->context->{checkout},
so the assertion was always trivially true.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:51 +00:00
Kyle M Hall 402b6559f8
Bug 41728: (QA follow-up) Match original blocker priority order
Reorder blocker checks to match the original AddReturn priority:
Withdrawn > Wrongbranch > Lost. Previously Lost was checked before
Wrongbranch, which changed behavior for items that were both lost
and at the wrong branch.

QA note: There are other checks in-between these, but I don't think
their order betwixt these blockers matters for retaining existing
behavior.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:49 +00:00
Kyle M Hall e822660311
Bug 41728: (QA follow-up) Populate checkout context before blocker checks
The availability check is returning early on blockers (BlockedWithdrawn,
BlockedLost, Wrongbranch) before checking if the item had an active
checkout. This causes AddReturn to lose checkout/patron context for
checked-out items that hit a blocker, resulting in incorrect NotIssued
messages, unintended onloan clearing, and incorrect local use recording.

Move the checkout check to run first so context is always populated
regardless of blockers.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:48 +00:00
Tomás Cohen Arazi cb3deeadcc
Bug 41728: (follow-up) Add transfer limit check and clean param names
- Rename params: branch => library, add to_library for destination
- Move $returnbranch computation before the availability check in
  AddReturn so it can be passed as to_library
- Add transfer limit check (bug 7376) to the Availability class
- Rename internal _can_book_be_returned to _check_return_policy
- Remove duplicate $returnbranch block and standalone transfer
  limit check from AddReturn
- Update tests for new param names

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:47 +00:00
Tomás Cohen Arazi 4b277a6e4a
Bug 41728: Make AddReturn use Koha::Item::Checkin::Availability
This patch refactors AddReturn to use the new Koha::Item::Checkin::Availability
class for validation logic, eliminating code duplication and centralizing
check-in validation.

Changes:
- Add use statement for Koha::Item::Checkin::Availability
- Look up item first, return BadBarcode if not found
- Call $item->checkin_availability() for validation
- Use Result object methods (available(), blockers, context)
- Replace inline withdrawn/lost/branch validation with blocker handling
- Remove duplicate CanBookBeReturned() call (now in Availability class)
- Preserve all existing behavior and error messages
- Maintain backward compatibility

The refactoring extracts ~40 lines of validation logic while maintaining
identical functionality. All validation results are obtained from a single
availability check that returns a Result object.

Note: The withdrawn message is set in two places depending on context:
- When BlockReturnOfWithdrawnItems is ON: set in blocker handler before early return
- When BlockReturnOfWithdrawnItems is OFF: set by existing check later in the flow
This preserves the original behavior where withdrawn items always get the
message, but are only blocked when the syspref is enabled.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Item/Checkin/Availability.t
=> SUCCESS: Tests pass!
  k$ prove t/db_dependent/Circul*
=> SUCCESS: Tests pass!
3. Verify check-in operations work correctly:
   - Items not found return BadBarcode
   - Withdrawn items blocked when syspref enabled
   - Lost items blocked when syspref enabled
   - Wrong branch returns blocked per AllowReturnToBranch
   - Not issued items handled correctly
   - Normal check-ins complete successfully
4. Sign off :-D

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:45 +00:00
Tomás Cohen Arazi 9b8328c3f5
Bug 41728: Add Koha::Item::Checkin::Availability class
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:44 +00:00
Tomás Cohen Arazi b5041ae361
Bug 41728: Add Koha::Availability::Result base class
This patch introduces a standardized result class for availability checks,
providing a clean API with proper methods instead of raw hashrefs.

The Koha::Availability::Result class provides:
- add_blocker() - Add conditions that prevent the action
- add_confirmation() - Add conditions requiring user confirmation
- add_warning() - Add informational messages
- set_context() - Store related objects (item, checkout, patron, etc.)
- available() - Check if action can proceed (no blockers)
- needs_confirmation() - Check if confirmations are required
- Accessor methods for all categories
- to_hashref() for backward compatibility

This design:
- Provides a consistent interface across all availability checks
- Makes the API more discoverable and self-documenting
- Enables future enhancements without breaking changes
- Follows OO principles with proper encapsulation
- Can be used as base for checkout, hold, renewal availability

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
  k$ prove t/Koha/Availability/Result.t
=> SUCCESS: Tests pass!
3. Verify all methods work correctly
4. Sign off :-D

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:43 +00:00
Tomás Cohen Arazi 78906e00da
Bug 42443: (QA follow-up) Use POST /transfers/{transfer_id}/cancellation
Cancelling a transfer is a state change on the resource, not a deletion of
it, so it should be modelled as an action sub-resource rather than a DELETE
on the transfer itself. This aligns the endpoint with the API coding
guidelines (REST2: non-CRUD actions use action sub-resources; REST3.2:
conflicts return 409) and with the design used in bug 43454.

Changes:
- Replace DELETE /transfers/{transfer_id} with
  POST /transfers/{transfer_id}/cancellation
- Rename the controller method delete() to cancel(), look the transfer up
  with objects->find_rs, and return 200 with the updated resource
  representation instead of 204 with an empty body
- Make cancellation_reason optional, defaulting to 'Manual', matching the
  legacy returns.pl behaviour
- Return 409 with a structured error_code ('already_arrived',
  'already_cancelled') when the transfer cannot be cancelled, instead of a
  bare 400
- Update the transfers to receive page to POST to the new endpoint and to
  treat 404/409 as "already gone, refresh the table"
- Adapt the tests accordingly, including coverage for the default reason
- The *_date API attribute names introduced by this bug are kept, as they
  are the guideline-compliant form (REST1.3.4.1: *_date, not date_*)

Test plan:
1. Apply patch
2. Run the tests:
   $ ktd --shell
  k$ prove t/db_dependent/api/v1/transfers.t
=> SUCCESS: Tests pass!
3. Rebuild and validate the API spec:
  k$ yarn api:bundle
  k$ prove xt/api.t
=> SUCCESS: Tests pass!
4. In the staff interface, go to Circulation -> Transfers to receive,
   click "Cancel transfer" on a row
=> SUCCESS: The transfer is cancelled and the row drops off the table
5. Cancel the same transfer again from a second tab
=> SUCCESS: The API returns 409 (already_cancelled) and the row is
   treated as already gone
6. Sign off :-D

Assisted-by: Sonnet 4.6 (Anthropic)
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:41 +00:00
Martin Renvoize d7a87e2cbd
Bug 42443: (QA follow-up) Use library selects for Sent from and Home library filters
Replace the free-text filter inputs for the "Sent from" and "Home library"
columns with select dropdowns populated from the full library list, consistent
with other DataTables pages such as the pending bookings page.

The "Sent from" column is made visible again (it was previously hidden since
rowGroup provides the visual grouping); showing it also allows its filter cell
to render.

The "On hold for" column intentionally has no filter: it is computed from two
different embedded sub-objects (item.first_hold.patron or item.recall.patron)
and has no single API field to filter against server-side.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:40 +00:00
Martin Renvoize dcb423b766
Bug 42443: (QA follow-up) Add DBIC relationship aliases on Branchtransfer
The REST framework resolves _order_by parameters and embed prefetches by
walking prefetch_whitelist, which is built from DBIC relationship names.
The auto-generated relationships use the FK column names (itemnumber,
frombranch, tobranch), but the API exposes them as item, from_library,
and to_library. Sorting by item.biblio.title etc. threw a 500 because
those names were not in the whitelist.

Adding belongs_to aliases below the DO NOT MODIFY line bridges the API
names to the DBIC names, matching the convention used by other Koha
schema result classes (e.g. Koha::Schema::Result::Item).

Also updates the relation methods in Koha::Item::Transfer to use the new
aliases consistently, so the accessor names line up across both layers.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:39 +00:00
Martin Renvoize ebb77c8fa9
Bug 42443: (QA follow-up) Group transfers by sending library using rowGroup
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:38 +00:00
Martin Renvoize e5a07ccd25
Bug 42443: (QA follow-up) Test from_library_id query filter
The OpenAPI spec documents from_library_id as a supported filter
parameter but the test suite only exercised to_library_id. Add a test
to verify filtering by source library returns the correct transfer.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:36 +00:00
Martin Renvoize 02a02c7209
Bug 42443: (QA follow-up) Test UseRecalls integration in transfer cancel
The delete() controller reverts any in-transit recall tied to the item
when UseRecalls is enabled. Add a subtest to verify the recall status
is set back to 'requested' after the transfer is cancelled.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:35 +00:00
Martin Renvoize 19d1db91d2
Bug 42443: (QA follow-up) Fix escaping of branchcode in JS context
Using | html to embed a TT value inside a JS string literal is
incorrect: it produces HTML entities rather than JS-safe characters.
Store the branchcode in a data attribute (where | html is correct)
and read it from there in JavaScript.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:34 +00:00
Martin Renvoize 16b2ac663e
Bug 42443: (QA follow-up) Guard nullable embed access in JS renderers
from_library and home_library can be null if a transfer references an
orphaned branch row. Accessing .name on null throws a TypeError and
crashes the DataTable renderer; guard both accesses.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:33 +00:00
Martin Renvoize f80b599332
Bug 42443: (QA follow-up) Add to_library to embed enum
Koha::Item::Transfer exposes a to_library() method and the definition
already listed to_library as a valid property, but the x-koha-embed
enum was missing the entry. Add it so consumers can embed it.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:31 +00:00
Martin Renvoize f8d59a7b50
Bug 42443: (QA follow-up) Prevent cancelling completed transfers
The delete endpoint was passing force => 1 to cancel() with no state
guards, allowing already-received and already-cancelled transfers to
be re-cancelled, corrupting datearrived/datecancelled audit fields.

Add explicit 400 responses for both cases and tests to cover them.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:30 +00:00
Kyle M Hall c813fb2574
Bug 42443: Add ability to cancel a transfer via the REST API
This patch adds a DELETE /transfers/{transfer_id} route that cancels the
given transfer by id and reverts an in transit recall for the item the same
way as returns.pl does. The cancellation reason is passed in the request body
so this api endpoint could be used in other places we can cancel transfers.

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:29 +00:00
Kyle M Hall 560d7b7ff7
Bug 42443: Convert transfers to receive page to ajax datatable
Updates circ/transferstoreceive.pl to use the new REST API with
an ajax DataTable to display transfer to receive.

Test Plan:
1) Apply this patch
2) Restart all the things!
3) Set an item in transit to your logged in library ( check it in at another
   branch so it is sent home, or place a hold to trigger a transfer )
4) Place a hold on a second transferred item, and a recall on a third
5) Browse to /cgi-bin/koha/circ/transferstoreceive.pl
6) Note the "On hold for" column shows the hold patron, the recall patron or
   "None"
7) Set TransfersMaxDaysWarning low and note the "days late" badge appears
8) Click "Cancel transfer" and note the transfer is cancelled
9) A library with no incoming transfers will get "No transfers to receive"

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:28 +00:00
Kyle M Hall 4fc5617615
Bug 42443: Add unit tests
Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:26 +00:00
Kyle M Hall 9dac6290d7
Bug 42443: Add REST API for item transfers
Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:25 +00:00
Lisette Scheer 2aa2cc0e7f
Bug 42330: (QA follow-up) Fix file permissions on new test and tidy
This patch
- Fixes the file permissions on the new test
- Removes a variable declaration from the new test that's unused
- Removes a new line from the end of staff-global.scss

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:24 +00:00
Jonathan Druart 6cb0f5161c
Bug 42330: Auto-tidy
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:23 +00:00
Jonathan Druart fc32990f49
Bug 42330: Add new template tools/restore-records.tt
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:21 +00:00
Jonathan Druart 210aa964f0
Bug 42330: Add an xt test to prevent new occurrences of <main> in staff templates
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:20 +00:00
Owen Leonard 248c0fe224
Bug 42330: (follow-up) Some corrections to sidebars
- The structure of the search facets, advanced MARC editor, and
  HTML customiztions sidebars need to be corrected in order for
  the collapse button to work correctly.
- The "toggle_sidebar" click handler should be inside a
  "$(document).ready()" in order to work correctly on pages where the JS
  is in the header.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:19 +00:00
Jonathan Druart ad694a3411
Bug 42330: Tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:17 +00:00
Jonathan Druart b02ca1270d
Bug 42330: Use main-container from all our templates
This goal here is to uniformize the structure of our templates.
Using main-container.inc will also introduce the sidebar collapse (from
bug 42322) where it was missing

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-04 11:23:13 +00:00
David Cook 0acaa7fd55
Bug 43202: Make it easier to find ERM system preferences
This patch makes key ERM system preferences contain the words
ERM, e-resource, and electronic resource to improve discoverability
when searching system preferences.

Test plan:
0. Don't apply the patch yet
1. Go to Administration and type "electronic" into "Search system
   preferences" and search
2. Note you get the message "No system preferences matched your search
   for: electronic"
3. Apply the patch
4. Search for "electronic" in the system preferences again
5. Note that "ERMModule" and "ERMProviders" appear

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:54:04 +00:00
Aleisha Amohia 37e6568a9b
Bug 32419: Show item availability on lists view
To test:

1. Create or use a biblio record with multiple items, for example biblionumber 235.
2. Check out an item attached to the record to a patron, i.e. check out barcode 39999000010190 to your logged in user
3. Go to the Detail view for this record.
4. Edit another item attached to the record and give it a not for loan status, i.e. edit barcode 39999000010213 and set it to Not for loan
5. Click the Add to list button above the record. Add the record to an existing list, or create a new one and Save.
6. Go to the Lists module in the staff interface and view the contents of the list you just added your record to.
7. Confirm you see accurate Availability information in the list results within the callnumber column.

Sponsored-by: Pymble Ladies' College
Signed-off-by: Hayley Cheng <hacheng@pymblelc.nsw.edu.au>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:54:03 +00:00
Baptiste Wojtkowski e827290b9b
Bug 41742: Replace depth attribute with budget_level in acquisition
Test Plan:
1. Pre-checks:
   a. Observe plack-intranet-error.log.
   b. Make sure you have one fund with a subfund which also have a
   subfund.
2. After each followings, verify there are new logs in
   plack_intranet-error.log, check that the fund dropdown contains no ">"
   a. In staff interface go to Acquisitions module.
   b. Navigate to Advanced Search
   c. In a basket create an order by clicking on "Add to basket" ->
   "From existing orders (copy)"
3. Apply patch
4. run prove t/db_dependant/Budgets.t
5. Redo 2. and notice there is no log anymore and subfunds are marked
   with >, subsubfunds with >>

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:54:02 +00:00
Baptiste Wojtkowski dbf3327cdb
Bug 41742: Remove CheckBudgetParentPerm
This function is not used in the code anymore

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:54:01 +00:00
Paul Derscheid 987f302777
Bug 42997: (QA follow-up) Keep on-site checkouts due at the end of the day
circulation.pl still replaced the due date of an on-site checkout of a
booked item with the booking end date: with SpecifyDueDate disabled
the end-of-today due date computed for on-site checkouts was
clobbered, and with it enabled the same happened whenever staff left
the due date field empty. An on-site loan could therefore be due back
days after the on-site session.

- never stretch an on-site checkout to the booking end date; the due
  date behaves exactly as for a non-booked on-site checkout and
  AddIssue shrinks the booking end_date to the actual due date instead

Test plan:
1. Enable OnSiteCheckouts, disable SpecifyDueDate
2. Book an item for a patron starting today
3. Check the item out to them with 'On-site checkout' ticked
4. Without this patch the checkout is due at the booking end date;
   with it the checkout is due at the end of today and the booking
   end date follows it
5. Repeat with SpecifyDueDate enabled and the due date field left
   empty; the due date follows the circulation rules instead of the
   booking end date

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:53:59 +00:00
Paul Derscheid 90281d5a1f
Bug 42997: (QA follow-up) Don't invert the booking period when syncing end dates
Collecting a booking early (within the bookings lead period) with a
due date override before the booking start syncs end_date to a date
that precedes start_date. Koha::Booking->store has no interval
validation and the clash checks' date windows never see the inverted
period, so the broken row is stored silently - reproduced via AddIssue
on this patchset.

- pull start_date back to the checkout date whenever the synced due
  date precedes it, in both AddIssue and AddRenewal, so the booking
  window follows the loan instead of inverting
- cover both paths in Circulation.t; the AddRenewal case needs the
  Biblio::check_booking exemption from the previous patch, as the
  synced window no longer overlaps the stored one

Test plan:
1. prove t/db_dependent/Circulation.t
2. Set bookings_lead_period to e.g. 5 for an itemtype and enable
   SpecifyDueDate
3. Book an item of that itemtype for a patron starting in 3 days
4. Check it out to them today with an explicit due date of tomorrow
5. Without this patch the booking is stored with end_date before
   start_date; with it both dates follow the checkout period

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:53:58 +00:00
Paul Derscheid 8bd9790e47
Bug 42997: (QA follow-up) Don't let a booking's own checkout block it in Biblio::check_booking
Koha::Biblio->check_booking counts current checkouts on bookable items
against the pool of available items. Like Item::check_booking before
this patchset, it made no exception for the checkout that belongs to
the very booking being validated, so updating an issued booking's
period could clash against its own loan whenever the new period no
longer overlaps the stored one (the item_id exemption for booked items
only catches bookings that overlap the window under test).

This is masked in the end_date syncs earlier in this patchset because
the synced window always contains the stored start_date; it surfaces
as soon as a sync moves the whole period, and would equally bite the
extension pathway on bug 42790.

- skip checkouts whose booking_id matches the booking being validated
  when counting occupied bookable items, mirroring the
  Item::check_booking exemption
- cover the blocking and non-blocking checkout cases in Biblio.t

Test plan:
1. prove t/db_dependent/Koha/Biblio.t

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:53:57 +00:00
Martin Renvoize 11ab3a12f7
Bug 42997: Honour explicit due date overrides for booked items at checkout
When checking out a booked item, circulation.pl unconditionally
replaced the due date with the booking end date, silently discarding
any due date staff had explicitly specified - even after they had just
confirmed the BOOKED_DUE_DATE_CLASH warning saying they wanted it.

The booking end date remains the default when no due date is
specified; an explicit override (with SpecifyDueDate enabled) is now
passed through to AddIssue, which syncs the booking end_date with it
where possible and keeps the original end date when the override would
clash with a subsequent booking (as warned).

Test plan:
1) Create a booking and check the item out without specifying a due
   date; confirm the due date defaults to the booking end date
2) Return the item, book it again, and check out specifying an
   explicit due date; confirm the checkout uses that date and the
   booking end date follows it

Sponsored-by: OpenFifth <https://openfifth.co.uk/\>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/\>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:53:56 +00:00
Martin Renvoize 50115d8322
Bug 42997: Sync booking end_date with the due date at renewal
When a checkout satisfying a booking is renewed, the booking previously
kept its original end_date, blocking the booked period from other
patrons for the wrong window and leaving the two records out of sync.

This patch updates C4::Circulation::AddRenewal to pull the linked
booking's end_date along with the renewed due date, mirroring the
behaviour introduced for AddIssue earlier in this patchset. As at
issue time, if the new due date would clash with a subsequent booking
(possible when the renewal was explicitly allowed past a clash), the
booking keeps its original end_date.

Together with the previous patch this makes a renewal the natural way
to extend an issued booking: CanBookBeRenewed refuses renewals that
would run into the next booking, and AddRenewal keeps the booking in
step with the loan.

Test plan:
1) Run t/db_dependent/Circulation.t and confirm the new
   'AddRenewal | booking end_date sync' subtest passes
2) Check out a booked item, renew it from the checkouts screen and
   confirm the booking end date follows the new due date

Sponsored-by: OpenFifth <https://openfifth.co.uk/\>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/\>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:53:55 +00:00
Martin Renvoize 2da3248b41
Bug 42997: Prevent renewals running into the next booking
CanBookBeRenewed checked bookings by asking Koha::Item->find_booking
for the first booking that clashes with the renewal period. When the
renewing patron's own booking satisfies this checkout, that booking is
returned first and renewal is allowed - subsequent bookings for the
item were never considered. A patron could therefore renew a booked
item straight through the start of the next patron's booking.

Additionally, when staff renew with an explicit due date (the renewal
due date input on the checkouts screen, or the hard due date on
circ/renew.pl), CanBookBeRenewed validated a *calculated* due date
whilst AddRenewal then applied the *requested* one, so the requested
date was never checked against bookings at all.

This patch:

* repeats the booking lookup with the patron's own booking excluded,
  refusing the renewal ('booked') when it would run into the next
  booking for the item
* adds an optional $date_due parameter to CanBookBeRenewed, used in
  place of the calculated due date for the bookings check, and threads
  the requested date through from svc/renew and circ/renew.pl

Test plan:
1) Run t/db_dependent/Circulation.t - the extended
   'CanBookBeRenewed | bookings' subtest covers both the calculated
   and requested due date cases
2) Check out a booked item to its patron, add a subsequent booking for
   another patron, and attempt to renew past the second booking's
   start date from the checkouts screen; confirm the renewal is
   refused as 'booked'

Sponsored-by: OpenFifth <https://openfifth.co.uk/\>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/\>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:53:53 +00:00
Martin Renvoize 9a4a9415ab
Bug 42997: Warn staff when a due date override runs into the next booking
When checking out a booked item, staff may override the suggested due
date. If that override runs into the next booking for the item, the
previous patch would silently keep the booking's original end date -
staff had no way of knowing their override crossed another booking.

This patch adds an exclude_booking_id option to Koha::Item->find_booking
so a lookup can skip a specific booking (here: the patron's own booking
being collected), and uses it in CanBookBeIssued to detect when an
explicitly requested due date would run into the next booking for the
item. When it would, a new BOOKED_DUE_DATE_CLASH confirmation is raised
and displayed on the checkout screen, telling staff the linked booking
will keep its original end date if they continue.

Only explicitly requested due dates trigger the confirmation -
calculated due dates are clamped to the booking end date later in the
staff checkout flow, so checking them would produce false positives.
SIP is unaffected as it never passes a requested due date.

Test plan:
1) Run t/db_dependent/Circulation.t and t/db_dependent/Koha/Item.t
2) Create two consecutive bookings on the same item for two patrons
3) Check the item out to the first patron specifying a due date beyond
   the second booking's start date; confirm the new confirmation
   message appears and checkout only proceeds once confirmed

Sponsored-by: OpenFifth <https://openfifth.co.uk/\>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/\>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:53:52 +00:00
Martin Renvoize 41c453eff9
Bug 42997: Don't let a booking's own checkout block it in check_booking
Koha::Item->check_booking refuses any booking period that starts before
the item's current checkout is due back. That is correct when assessing
a new booking against someone else's loan, but it also fired when the
checkout in question is the booking's own linked loan - making it
impossible to ever move an issued booking's end_date, as any update
would 'clash' with its own checkout.

This patch skips the checkout guard when the checkout's booking_id
matches the booking_id passed for exclusion, i.e. when the checkout
belongs to the very booking being updated. This is a prerequisite for
keeping booking end dates in sync with checkout due dates in this
patchset, and for the booking extension pathway on bug 42790.

Test plan:
1) Run t/db_dependent/Koha/Item.t and confirm the check_booking
   subtest passes, including the two new checkout-related tests

Sponsored-by: OpenFifth <https://openfifth.co.uk/\>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/\>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:53:51 +00:00
Martin Renvoize 67e0051e1f
Bug 42997: Sync booking end_date with due date override at checkout
When a booked item is checked out, the due date defaults to the booking
end_date. Staff may override the due date at checkout time, which
previously left the booking end_date out of sync with the checkout,
blocking the booked period for longer or shorter than the actual loan.

This patch updates C4::Circulation::AddIssue to sync any due date
override back to booking.end_date the moment the booking transitions to
'issued', keeping the booking and the checkout aligned from the point
of issue. The existing Koha::Booking->store clash detection is used to
guard the update: if the overridden due date would clash with a
subsequent booking for the same item, the checkout proceeds with the
booking's original end_date instead.

Test plan:
1) Run t/db_dependent/Circulation.t and confirm the new
   'AddIssue | booking end_date sync with due date override' subtest
   passes
2) Create a booking for a patron on a specific item
3) Check the item out to the patron, overriding the suggested due date
4) Confirm the booking's end_date now matches the overridden due date

Sponsored-by: OpenFifth <https://openfifth.co.uk/\>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/\>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:53:50 +00:00
Marcel de Rooy 3ec3c465ba
Bug 36001: Show MARC21 362 on OPAC and staff
Test plan:
Find a serial.
Add a 362 with some content in $a
Check the normal view on OPAC and staff.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: sabrina <sabrina.kiehl@gwdg.de>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 15:53:46 +00:00
Brendan Lawlor b7df6f071f
Bug 38196: (QA follow-up) Rename Include as Show
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:57:01 +00:00
Jan Kissig 54c5c2a9c4
Bug 38196: (follow-up) Fix issue with concern filters containing underscores
This fixes the issue that a filter for a ticket status is not applied when the underlying authorised value for that ticket status contains an underscore.
Also the fa icons are changed to be more intuitive.

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:57:00 +00:00
Jan Kissig f14a046bb1
Bug 38196: (follow-up) Rename Filter to Hide in concerns.tt
This changes the filter names in concerns.tt from "Filter ..." to "Hide ..." to clarify that a filter here will remove items with that value from the table.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:56:59 +00:00
Jan Kissig 69217a2bc4
Bug 38196: Fix filter in catalog concerns to show tickets where status is NULL
This patch fixes issues on catalog concerns when filtering for self defined authorized values (AV).
When querying for status != AV, also rows with status = NULL are omitted as SQL here only checks for defined values in status.
The patch wraps the a filter query with an 'or status is NULL'.

Test plan:
apply patch dependencies (42283) if not already included and run restart_all
a) enable system preference CatalogConcerns
b) create some authorised values in TICKET_STATUS
c) create several concerns/tickets (biblio detail page -> new catalog concern)
d) go to /cgi-bin/koha/cataloguing/concerns.pl and change some ticket status to have some variation for filtering
e) play with the filters on the top (resolved and self created TICKET_STATUS)
- when clicking 'Filter resolved' tickets with status 'Resolved by ... date' get filtered
- when filtering self created TICKET_STATUS values tickets with that status AND tickets with status Open are disappearing
f) apply patch and run restart_all
g) retry steps in e) and check that only the filtered status are filtered from the results list.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:56:58 +00:00
Pedro Amorim f4bd0afa1b
Bug 39829: DBRev 26.06.00.019
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:56:57 +00:00
Pedro Amorim 1cf15f3c6e
Bug 39829: dbic
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:56:56 +00:00
Paul Derscheid a70df61623
Bug 39829: (QA follow-up) Fix test plan count
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:56:54 +00:00
Paul Derscheid 64aa56877a
Bug 39829: (QA follow-up) Pass server through for the title field
add_field/maybe_add need $server to apply the hide_fields and
allow_fields account options; without it the AJ field is always sent.

- Pass $server at the FID_TITLE_ID call sites in the checkout, checkin,
  item information, item status update and renew responses

Assisted-by: Fable 5 (Anthropic)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:56:53 +00:00
Nick Clemens f1ca516b60
Bug 39829: Allow custom title in SIP responses
This patch adds a custom aj_field_template to the sip accounts
This allows libraries to specify a template for the title (AJ) field in sip
messages

The template allows using item and biblio fields (item provides access to biblio. but we pass
both in order to makes the templates simpler

To test:
1 - Install patches
2 - updatedatabase, yarn build, restart_all
3 - Setup a sip account, KTD already has a sip patron, term1, you can use that
    don't add the aj_field_template yet
4 - Find or create a biblio with 245a and 245b like 245$aTitle : $bSubtitle and add item with TESTBARCODE
5 - From the command line:
    perl misc/sip_cli_emulator.pl -su term1 -sp term1 -l CPL -t CR -a localhost -p 6001 -m item_information --item TESTBARCODE
6 - Confirm AJ field shows "Title :" as expected
7 - Edit sip account, set aj_field_template to:
    [% biblio.title %] [% biblio.subtitle %] [% item.barcode %]
8 - Repeat the sip test
9 - Confirm you get the response like:
    Title : Subtitle TESTBARCODE
10 - Sign off!

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:56:52 +00:00
Jonathan Druart 6a040f8003
Bug 42975: (bug 35380 follow-up) Fix patron category filtering on Record overlay rules
Test plan:
Turn on MARCOverlayRules
Go to /cgi-bin/koha/admin/marc-overlay-rules.pl
Confirm the "Filter" column is correctly adjusted when the "source"
dropdown is modified

Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:56:50 +00:00
Tomás Cohen Arazi b31751a6cd
Bug 42565: Fix uninitialized value warning in GetIssuingCharges
GetIssuingCharges overwrites its initial $charge = 0 with the value of
itemtypes.rentalcharge, which is NULL when no rental charge is configured.
This causes 'uninitialized value' warnings in every caller that compares
$charge > 0 (automatic renewals, SIP checkout/renew, OPAC).

The fix normalizes NULL to 0 at the source. All callers already treat
the return value as numeric, so this is safe. Also removes a now-redundant
$charge && guard in AddIssue that was working around this.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Circulation.t
   => SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Chris Mathevet <chris.mathevet@inLibro.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:56:49 +00:00
Tomás Cohen Arazi 1839189b1c
Bug 35142: (follow-up) Fix detail page crash with invalid metadata
The staff catalogue detail page called $biblio->normalized_upc,
normalized_ean, and normalized_oclc unconditionally. These methods
go through metadata_extractor which calls $biblio->metadata->record
internally, re-fetching the (corrupt) metadata from the DB and
ignoring the already-sanitized $marc_record.

When a record has invalid MARCXML, this causes an unhandled exception
instead of displaying the 'biberror' warning.

Fix: use the Extractor directly with the already-recovered
$marc_record (which was sanitized via StripNonXmlChars), and guard
the block with a check that $marc_record is defined.

Test plan:
1. Apply patch
2. Run:
   $ ktd --proxy --selenium up -d
   $ ktd --wait-ready 120
   $ ktd --shell
   k$ prove t/db_dependent/selenium/regressions.t
=> SUCCESS: Tests pass (including 'detail page must not explode')
3. Sign off :-D

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-03 10:56:48 +00:00
Pedro Amorim 481e170da1
Bug 42968: Revert "Bug 42968: Add fallback label for 505 contents notes"
This reverts commit 3bdecfb4e6.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:52:24 +00:00
Laura_Escamilla 3bdecfb4e6
Bug 42968: Add fallback label for 505 contents notes
To test:

1. Create or locate a bibliographic record with a MARC 505 field.

2. Leave the first indicator blank.

3. Add text to the 505 field.

4. View the record detail page.

5. Notice that the 505 contents display without a label.

6. Apply the patch

7. Refresh the record detail page. Notice that the 505 content displays a label now.

8. Sign off and have a lovely day! :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:19 +00:00
Pedro Amorim 9d0b71511b
Bug 42653: Add tests
prove t/db_dependent/Koha/ILL/Request.t
prove t/db_dependent/Koha/Plugins/ILL/Backends.t

NOTE: These must be run without having any ILL backend plugin installed

Signed-off-by: Hannah Prince <helm.consortium.nhs.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:18 +00:00
Pedro Amorim 0e95900335
Bug 42653: get_backend_plugin should cache results per backend name
The plugin cache used a single slot, so the first backend looked up
poisoned it for all subsequent calls. This caused 500 errors when
viewing ILL requests that had been migrated between backends.

Test plan:
1) Download and install 2 additional plugin backends that implement
their own templates:
https://github.com/openfifth/koha-ill-libkey-lending-tool/releases/download/v2.6.1/koha-ill-libkey-lending-tool-v2.6.1.kpz
https://github.com/openfifth/koha-ill-reprintsdesk/releases/download/v4.2.0/koha-ill-reprintsdesk-v4.2.0.kpz

2) Enable ILLModule and run the following SQL:

INSERT INTO illrequests (borrowernumber, branchcode, status, backend, placed)
SELECT b.borrowernumber, br.branchcode, 'COMP', 'ReprintsDesk', NOW()
FROM borrowers b, branches br
LIMIT 1;

INSERT INTO action_logs (timestamp, user, module, action, object, info, interface)
VALUES
 (NOW() - INTERVAL 2 HOUR, 1, 'ILL', 'INCDOCS_REQUEST_PLACED', 1,
  '{"log_origin":"IncDocs","response":1}', 'intranet'),
 (NOW() - INTERVAL 1 HOUR, 1, 'ILL', 'REPRINTS_DESK_REQUEST_ORDER_UPDATED', 1,
  '{"log_origin":"ReprintsDesk","response":null}', 'commandline');

3) Attempt to open the 'Manage request' page for this newly created
request:
http://localhost:8081/cgi-bin/koha/ill/ill-requests.pl?op=illview&illrequest_id=1

4) Notice you get a 500 error:
Template process failed: file error - : not found

5) Apply all patches, restart plack, refresh. Notice the 'Manage
request' page now renders correctly.

6) Click the 'ILL request log' toolbar button. Confirm that both the
IncDocs and ReprintsDesk entries are shown.

Signed-off-by: Hannah Prince <helm.consortium.nhs.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:17 +00:00
Martin Renvoize 6d8ea60630
Bug 42993: EDI: log transport failures via Koha::Logger instead of carp
Koha::Edifact::Transport reported every transport failure (no transport
configured, connect failure, directory-change failure, file-listing
failure and per-file download/upload failure) with carp. Under
edi_cron.pl these go to STDERR, so on a normal cron run the messages are
lost and EDI simply goes quiet with nothing in editrace.log to explain
why.

Route these failures through Koha::Logger on the 'edi' interface (the
same interface edi_cron.pl already uses) so they are recorded in the EDI
log. The two ingest()-level data warnings (duplicate filename, unreadable
file) are intentionally left as carp: one is asserted by the existing
unit test and neither is a transport failure.

No change to the transfer logic.

Test plan:
1) prove t/db_dependent/Koha/Edifact/Transport.t
2) Point an EDI account at an unreachable transport and run edi_cron.pl;
   confirm the failure is now written to the edi log rather than only to
   STDERR.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:15 +00:00
Jonathan Druart 02491b08d3
Bug 43394: DT - Cast pageLength to Integer
In "Edit patron card batch" the pageLength is coming from the syspref,
the value is a string.
But DataTable's needs an integer.
Let's fix this in datatables.js, in the kohaTable's constructor

Test plan:
Got to cgi-bin/koha/patroncards/edit-batch.pl
Enter more than 20 borrowernumbers and "Add patron(s)"
Click "next" and notice that with this patch the pagination works.
Without this patch the JS error is
"Uncaught TypeError: can't access property "nTr", p is undefined"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:14 +00:00
Pedro Amorim eb81e1cc5e
Bug 43072: DBRev 26.06.00.018
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:13 +00:00
Emily Lamancusa dcba10c45d
Bug 43072: (QA follow-up) Use say_info in dbrev
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:12 +00:00
Andrew Fuerste Henry 17c1c87583
Bug 43072: standardize atomicupdate formatting
Signed-off-by: Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:11 +00:00
Nick Clemens 241abf5545
Bug 43072: (Bug 41267 follow-up) Add default rules when not set
Bug 41267 defined the new rule, but didn't set the default.

For consistencies sake we should create these in the DB

To test:
1 - Create 'Default holds and bookings policies by item type' for two itemtypes
    Set 'Fill other record level holds on record at checkout' to yes for one and no for the other
2 - Apply this patch
3 - updatedatabase
4 - Verify the rules are not altered
5 - Chekc the DB:
    SELECT * FROM circulation_rules WHERE branchcode IS NULL and categorycode IS NULL;
6 - Note the number of rules/ids
7 - Run the DB update and confirm nothing changes
8 - DELETE from circulation_rules WHERE rule_name='fill_other_biblio_holds_policy';
9 - Run the DB update
10 - Confirm rules are replaced, both as yes
11 - Delete a single one of the rules now:
    DELETE from circulation_rules WHERE rule_name='fill_other_biblio_holds_policy' AND id=#;
12 - Run the DB update
13 - Confirm only that rule is replaced
14 - DELETE from circulation_rules WHERE branchcode IS NULL and categorycode IS NULL;
15 - Run the DB update
16 - No new rules are created

 follo#	Koha/.Patrons.pm.swp

Signed-off-by: Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:10 +00:00
Andrew Fuerste Henry 0c86df4755
Bug 43072: Show Yes when no rule exists for fill_other_biblio_holds_policy
To test
1 - have three test biblios. Bib A has just one item, a Book. Bib B has two items, a Book and a Computer File. Bib C has one item, a Computer File
2 - have KTD default circ rules (holds allowed for all itemtypes in the circ rules table, no values defined in "Default checkout, hold and return policy" or "Default holds and bookings policies by item type
")
3 - set "Default holds and bookings policies by item type" for Computer Files to Hold Policy = "No holds allowed."
4 - observe that "Fill other record level holds on record at checkout" defaults to "Yes" when a value is set to Hold Policy
5 - confirm you can place a holds on Bib A and Bib B but not Bib C
6 - confirm your hold on Bib B is filled when checking out the Computer File item from Bib B
7 - update "Default holds and bookings policies by item type" so Computer Files has Hold Policy = "No holds allowed" and "Fill other record level holds on record at checkout" = No
8 - confirm you're still able to place a hold on Bib B
9 - confirm your hold on Bib B is now *not* filled by checking out the Computer Files item
10 - open the commandline SQL interface (sudo koha-mysql kohadev on KTD)
11 - run query "select * from circulation_rules where rule_name = 'fill_other_biblio_holds_policy'"; confirm an entry for Computer Files with rule_value=0
12 - run command "delete from circulation_rules where rule_name = 'fill_other_biblio_holds_policy'"
13 - reload your circ rules page, confirm the interface still shows "Fill other record level holds on record at checkout" = No for comoputer files
14 - confirm your hold on Bib B can again be filled by checking out the Computer File item

 APPLY PATCH, restart_all

15 - reload circ rule page, confirm it now shows "Fill other record level holds on record at checkout" = Yes for Computer Files
16 - run query "select * from circulation_rules where rule_name = 'fill_other_biblio_holds_policy'"; confirm there is still no entry for Computer Files
17 - place a new hold on Bib B, confirm it fills by checking out the Computer File (not having any rule defined is enforced the same as a "Yes" and displays a "Yes")
18 - in circ rules, set "Fill other record level holds on record at checkout" = Yes for computer files
19 - run query "select * from circulation_rules where rule_name = 'fill_other_biblio_holds_policy'"; confirm there is an entry for Computer Files with rule_value=1
20 - place a new hold on Bib B, confirm it fills by checking out the Computer File (having an explicit Yes rule defined is enforced correctly and displays correctly)
21 - in circ rules, set "Fill other record level holds on record at checkout" = No for computer files
22 - run query "select * from circulation_rules where rule_name = 'fill_other_biblio_holds_policy'"; confirm there is an entry for Computer Files with rule_value=0
23 - place a new hold on Bib B, confirm it does not fill by checking out the Computer File (having an explicit No rule defined is enforced correctly and displays correctly)

Signed-off-by: Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:09 +00:00
David Cook ddd790e4c5
Bug 42924: Restore default MaxAge for CSRF token
This patch changes the default MaxAge from 8 hours to
168 hours or 7 days due to usability issues encountered from
using a short-lived token.

See OWASP for more information on why timestamps with expiry
are not necessary for CSRF tokens. The MaxAge is a requirement
of the WWW::CSRF token used by Koha, so let's just use a longer
expiry in lieu of no expiry.

Test plan:
0. apply patch
1. prove t/Token.t

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:08 +00:00
Martin Renvoize bfc6866200
Bug 39589: Auto-skip to the next page and note hidden results when a page is fully filtered
When every result on a page is hidden (OpacHiddenItemsHidesRecord), rather
than leaving the patron on a dead-end "not available" page, look ahead a
bounded number of pages (3) for one with visible results and redirect
there directly. The lookahead is capped so a library with many
consecutive hidden pages doesn't turn a single search into a long chain
of extra search-engine queries; RSS/atom/opensearchdescription requests
are left alone since redirecting a feed reader makes no sense.

Whenever a hidden page was skipped over (whether we found a visible page
to land on, or exhausted the lookahead and fell back to the "not
available" message), show a caveat next to the results count so the
"Your search returned N results" figure doesn't read as contradicting
what the patron can actually see - this was raised as a sign-off concern
on the original patch (comment 4).

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:07 +00:00
Martin Renvoize a8523740b0
Bug 39589: (QA follow-up) Revert unrelated star rating markup change
The read-only star rating widget in search results was changed from
<span> to <a href="#"> elements, unrelated to this bug's fix. That
activated a pre-existing but previously dead click handler
($(".br-readonly a") in this same template) and turned every star icon
into a focusable, non-functional link, adding up to 5 dead tab stops
per search result. Restore the <span> markup used everywhere else this
read-only widget appears.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:06 +00:00
Martin Renvoize 84831855d2
Bug 39589: (QA follow-up) Don't zero hits when a page has no visible results
Zeroing $hits alongside the no_page_results flag also zeroed $total
(since $total accumulates $hits, and the final template 'total' param
is set from $hits/$total). Because the top "No results found!" banner
and the entire search-results section are both gated on 'total' being
true, this meant the new no_page_results branch could never actually
render: whenever it was true, 'total' was false, so the classic
"No results found!" page won instead - recreating the exact bug this
patch is meant to fix.

Verified against the bug's own test plan (OPACnumSearchResults=5,
OpacHiddenItemsHidesRecord, OpacHiddenItems: withdrawn:[1], 6 bibs with
the first 5 withdrawn/hidden): before this change, page 1 still showed
"No results found!" with no pagination; after this change, it shows
"Your search returned 6 results" / "Search results on this page are
not available", with a working pagination link to the page containing
the visible record.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:05 +00:00
Chris Mathevet ff26e80dc8
Bug 39589: (follow-up) Applied suggested changes
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:04 +00:00
Hammat Wele 38e0b734c9
Bug 39589: OPAC search returns 'No results found' despite existing matching records
This issue occurs when the records on the first page of results are not displayed. The message "No results found" is shown, and it is not possible to access records on other pages.

To test

    1. Set the following system preferences:
        OPACnumSearchResults to 5
        OpacHiddenItemsHidesRecord to Hide
        OpacHiddenItems to withdrawn: [1]
    2. Create 6 bibliographic records with the following titles:
        "notice test 1"
        "notice test 2"
        "notice test 3"
        "notice test 4"
        "notice test 5"
        "notice test 6"
    3. For the first 5 records, add an item with Withdrawn status.
    4. For "notice test 6", add an item with Available status.
    5. In the OPAC interface, search for "notice test".
    6. Apply the patch
    7. Repeat step 4
       ---> The results table is now shown with the message: "Search results on this page are not available."
       ---> You can now access the second page and see "notice test 6"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:03 +00:00
Jonathan Druart 3934a2bcc0
Bug 43117: Restore use Test::NoWarnings
From
  commit 6cc683b518
  Bug 34776: Unit tests

-use Test::NoWarnings;
-use Test::More tests => 2;
+use Test::More tests => 1;
+use Test::MockModule;
+#use Test::NoWarnings;

Test plan:
Apply the first patch
 prove xt/use-test-no-warnings.t
=> FAIL
Apply this patch
 prove xt/use-test-no-warnings.t
=> PASS
 prove t/Form_MessagingPreferences.t
=> PASS

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:02 +00:00
Jonathan Druart e2c90b5750
Bug 43117: Adjust xt test to catch commented use Test::NoWarnings
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:01 +00:00
Andrii Nugged fd6a616428
Bug 43354: Fix ILL request table search
Bug 33544 combined the ILL request detail fields into one column. The
combined column kept an empty data property without the related-object
search metadata. A global table search therefore generated a query against
"me.", which the REST endpoint rejected with a 500 error.

Restore the extended attribute search mapping for the fields displayed in
the combined Request details column. Add Cypress coverage for both the REST
query and the matching result.

Test plan:
Before applying the patch:
1. Make sure the ILLModule system preference is enabled.
2. Open a patron record.
3. Click "ILL requests history" in the menu on the left.
4. Type any text in the Search box above the table.
5. Confirm that Koha shows a 500 Internal Server Error message.

Apply the patch.

After applying the patch:
1. Repeat steps 2 to 4 above.
2. Confirm that the table refreshes without an error. It may show no
   matching requests.
3. If the patron has an ILL request, search for part of a title or author
   shown under Request details.
4. Confirm that the matching request remains in the table.

Co-Authored-By: Loontik <loontik@nugged.team>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:42:00 +00:00
Jonathan Druart 274432cd68
Bug 43259: Fix add/remove basket to/from group
Caused by
  commit e9b8004d19
  Bug 38255: Replace dataTable constructor with kohaTable - Staff

grouped and ungrouped should be DataTable objects.

Test plan:
Create a basket, close it
Create a basket group and test the "Add to group" and "Remove" button on
the basket group edit view

Without this patch there was a JS error:
"Uncaught TypeError: grouped.row is not a function"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-09-01 15:41:56 +00:00
Pedro Amorim 8870a456dc
Bug 30144: dbic
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:46 +00:00
Jonathan Druart e42b4df37b
Bug 42661: Remove redundant library's name if HidePatronName
If HidePatronName is turned on and a hold is being placed on a hold for
a patron with a different library, there is an alert the duplicate the
library's name:
Pickup library is different. Patron: A patron from library Springfield Patron's home library: (Springfield / SPL)

With this patch:
Pickup library is different. Patron's home library: Springfield / SPL
Pickup library is different. Patron: Keith Hendrix Patron's home library: Springfield / SPL

Test plan:
Go to /cgi-bin/koha/reserve/request.pl?biblionumber=117&borrowernumber=26
And confirm that the changes make sense

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:45 +00:00
Jonathan Druart 6e162e4c10
Bug 42727: Sort using the order from the staff syspref
The main goal of the previous patch was to prevent the randomness,
the languages were displayed by code.

The languages in the footer are the languages enabled for the staff interface.

This patch suggests to use the order of the syspref, and append the
languages not enabled at the end of the list.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:44 +00:00
Jonathan Druart a098d97b5a
Bug 42727: List languages in a determinist order
The list of languages are displayed in a random order in:
* language picker at the bottom of the page
* language tab when editing a notice template or an additional content

This patch fixes it to display them in a determinist order

Test plan:
Have several languages installed, and some of them enabled, change the
order of the languages in the syspref
Reload the mainpage several times and notice that the languages are
always displayed in the same order.
Same when editing a notice template or an additional content.

Note that I have not investigated yet why on the notice template and
additinal content edit views they are not listed in the same order.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:43 +00:00
Jonathan Druart 0f7145e4af
Bug 42727: Add tests
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:42 +00:00
Owen Leonard 0a5919f108
Bug 43257: Remove event attributes from circulation.tt
This patch updates circulation.tt in order to remove event attributes in
favor of defining event handlers in the relevant JS file.

To test, apply the patch and clear your cache if necessary.

- If necessary, enable the SpecifyDueDate system preference.
- Check out to a patron.
- Click the checkout settings icon inside the barcode field.
- Click the "Specify due date" field's "Remember for session" checkbox
  - The focus should move to the barcode field.

- Enable recalls if necessary
- Locate a checked out item which can be recalled.
- Log in to the OPAC and recall the item.
- In the staff interface, try to check that item out to another patron.
  - You should get a "Please confirm checkout" message.
  - Click the "Don't check out and print slip" button.
  - A popup should open for printing a recall slip.
  - The checkout page should reload  without checking out the item.

- If necessary, place a hold on an item for delivery to your logged-in
  library, check in the item, and confirm the hold.
- Try to check out the item to another patron.
  - You should get a "Please confirm checkout" message.
  - Click "Don't check out, confirm hold, and print slip"
  - A popup should open for printing a hold slip.
  - The checkout page should reload  without checking out the item.

- If necessary, place a hold on an item but do not check it in.
- Try to check out the item to a patron.
  - You should get a "Please confirm checkout" message.
  - Click "Cancel checkout and place hold for..."
  - You should be redirected to the holds page, placing a hold for that
    patron and that item.
  - The item should not have gotten checked out in the process.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Olivia Reynolds <olivia.reynolds@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:41 +00:00
Jonathan Druart b1905338eb
Bug 43373: Holdings table - hide the shelving location column if empty
The shelving location cell is always built with data: an empty HTML tag
is added even if there is no location.
So the "Shelving location" column is always displayed, it should only be
displayed if there are items with data.

Test plan:
Have a biblio with all items without shelving location (or without
several pages and at least one page without data).

If all rows have empty shelving loc, the column should be hidden

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:40 +00:00
Jonathan Druart d80ca5f4e8
Bug 42875: Correct failure message when adding an item to a bundle
If bc_1 is checked out and I try to add bc_2 I get:
"Failure: Item 'bc_2' belongs to another bundle"

It should be:
"Failure: Bundle is currently checked out"

Test plan (using KTD):
1. Pick a record with items, for example: The definitive guide to Catalyst (146)
2. Edit the record and change 000 (the leader):
   - Click the tag editor
   - Change "7- Bibliographic level" to "C- Collection"
3. In the holdings table there is now a "Manage bundle (0|0)" option
   in the far right column for each item.
4. Check an item out for the record out, for example
   I checked out 39999000006117 to Mary Burton.
5. In the holdings table for the record, click on "Manage bundle" for the item
   you checked out (39999000006117).
6. In the expanded text area below the item row, click "+ Add to bundle".
7. In pop-up window, enter 39999000006131 "Item barcode" field (this is one
   of the other items for the record that is not checked out).
8. Click Submit.
9. You get an error:
     Failure: Item '39999000006131' belongs to another bundle
10. Apply the patch.
11. Restart everything: restart all
12. Hard refresh the page for the record.
13. Repeat steps 5 to 8.
14. You now get the correct error:
      "Failure: Bundle is currently checked out"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:39 +00:00
Jonathan Druart 0c952e9e09
Bug 43241: Add tests for timezone support in RFC3339 q= queries
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:38 +00:00
Tomás Cohen Arazi fd87c5f195
Bug 43241: (follow-up) Use form => for q= params in tests
The + character in RFC3339 timezone offsets (e.g. +00:00) is decoded
as a space when interpolated directly into a URL query string. Use
Test::Mojo form => { q => ... } which properly URL-encodes the
parameter value.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:37 +00:00
Tomás Cohen Arazi 450e6bf178
Bug 43241: Fix datetime/boolean conversion in q= queries with DBIC operators
_parse_dbic_query maps field names but did not convert values. When
datetime or boolean filters are nested inside DBIC operators (-and,
-or) in the q= parameter, attributes_from_api (which normally handles
the conversion via _recursive_fixup) cannot reach them because it only
iterates top-level keys.

This patch fixes the issue in two places:

1. _parse_dbic_query (Query.pm): When a key maps to a known column
   (on the primary table or a related object), call _recursive_fixup
   to convert RFC3339 timestamps to MySQL format and booleans to 0/1.
   Related object instances are cached on the result_set to avoid
   repeated prefetch_whitelist lookups during recursive parsing.

2. Objects.pm: Move attributes_from_api to process named query params
   before merging with q= params. This avoids double-processing since
   q= values are already fixed up by _parse_dbic_query.

Test plan:
1. Apply the regression tests from the previous commit
2. Run:
   $ prove t/Koha/REST/Plugin/Query.t
   $ prove t/db_dependent/Koha/REST/Plugin/Objects.t
=> FAIL: Tests fail!
3. Apply this patch
4. Repeat step 2
=> SUCCESS: Tests pass!
5. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:36 +00:00
Tomás Cohen Arazi 811e57c441
Bug 43241: Add tests for datetime/boolean fixup inside DBIC operators
This patch adds regression tests proving that RFC3339 datetime values
and boolean values nested inside DBIC operators (-and, -or, -between)
in q= queries are not converted to their DB-native format.

Unit tests (Query.t):
- Datetime: >=, <=, range, -or, -between, nested -and/-or, me. prefix
- Boolean: true/false inside -and, -or, nested -and within -or
- Non-datetime values pass through unchanged

Integration tests (Objects.t):
- Full API stack tests using action_logs with controlled data
- Same operator/nesting coverage as unit tests

Test plan:
1. Apply patch
2. Run:
   $ prove t/Koha/REST/Plugin/Query.t
   $ prove t/db_dependent/Koha/REST/Plugin/Objects.t
=> FAIL: Tests fail! Values inside -and are not converted
3. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:35 +00:00
Martin Renvoize 537a64a01b
Bug 42634: Force JWT cookie deletion on self-checkout logout
The web-based self-checkout (opac/sco/sco-main.pl) builds the
response JWT cookie with -expires => $jwt ? '+1d' : ''. On logout
$jwt is undef, so the cookie is sent with an empty value and no
Expires attribute - i.e. a session cookie with an empty value
rather than a deletion. Some browsers/proxies retain the previous
valid JWT in this scenario, which presents as the Finish/close
button "doing nothing" or working only intermittently: the patron
appears to remain logged in despite clicking Finish.

Use a past Expires ('-1d') when no JWT is present so the browser
deletes the cookie entirely. The subsequent ?op=logout render
cannot then rediscover the patron from a stale cookie.

Test plan:
1. Enable web-based self-checkout (SelfCheckoutByLogin or
   SelfCheckAllowByIPRanges).
2. Log in as a patron at /cgi-bin/koha/sco/sco-main.pl.
3. Open DevTools -> Application -> Cookies and note the JWT cookie.
4. Click the green "Finish" button.
5. Without the patch: Set-Cookie on the ?op=logout response shows
   JWT= with no Expires; the cookie persists as an empty session
   cookie.
6. With the patch: Set-Cookie shows JWT=; expires=<past date>; the
   browser removes the cookie entirely.
7. Confirm the kiosk reliably returns to the login screen and
   subsequent page loads do not re-authenticate from a stale JWT.

Sponsored-by: OpenFifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:34 +00:00
Andrii Nugged b719532f24
Bug 42605: Add automated tests
Store scalar preference values when saving the acquisition preferences
and clean up the invoice explicitly.

Exercise the rendered receiving behavior for a basket that inherits
AcqCreateItem, one that explicitly overrides it with "ordering", and a
basket that creates items on receiving. Keep the explicit "ordering"
case as a control for behavior that already works without the production
fix.

This changes tests only.

Automated tests:

yarn cypress run --spec \
  t/cypress/integration/Acquisitions/OrderReceivePreferences_spec.ts

Co-Authored-By: Loontik <loontik@nugged.team>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:33 +00:00
Andrii Nugged 757b347bed
Bug 42605: Preserve acquisition preference string values
Bug 41563 converted AcqCreateItem and UniqueItemFields to booleans when
adding them to Koha.prefs. Both preferences are strings: AcqCreateItem
controls where items are created, while UniqueItemFields contains a
pipe-separated list.

When a basket inherits AcqCreateItem=ordering, the boolean value matches
none of the receiving page branches and existing items are hidden. When
items are created on receiving, the numeric UniqueItemFields value also
fails when the page calls split().

Preserve both string values in Koha.prefs. Add a Cypress regression test
which renders orderreceive.pl and verifies the values exposed to
JavaScript.

Test plan:

Before applying the patch:

1. Set AcqCreateItem to "placing an order" and UniqueItemFields to
   "barcode".
2. Create a basket that inherits the system preference. Add an order with one
   item, close the basket, and start receiving it.
3. Confirm that the Items table has no existing item row and no Receive
   checkbox.
4. Set AcqCreateItem to "receiving an order" and open the same order for
   receiving.
5. Confirm that the Items section is empty: no item-entry fields and no Add
   item button are shown.

Apply the patch.

After applying the patch:

1. Set AcqCreateItem back to "placing an order" and open the order for
   receiving again.
2. Confirm that its existing item is listed and has a Receive checkbox.
3. Set AcqCreateItem to "receiving an order" and open the same order again.
4. Confirm that the Items section contains item-entry fields and an Add item
   button.

Co-Authored-By: Loontik <loontik@nugged.team>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:32 +00:00
Tomás Cohen Arazi 3dc90004ca
Bug 39769: Flush L1 cache in es_indexer_daemon.pl before each batch
The daemon runs as a long-lived process and never clears its L1 cache.
System preference changes (e.g. IncludeSeeFromInSearches) are not
picked up until the daemon is manually restarted.

Flush L1 caches at the start of each commit() call so that preference
changes take effect on the next indexing batch without requiring a
daemon restart.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:31 +00:00
Pedro Amorim 279448539c
Bug 30144: DBRev 26.06.00.017
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:29 +00:00
Martin Renvoize 08a2e0475b
Bug 30144: (QA follow-up) Exclude servicing_instruction from order API representation
The servicing_instruction column was not mapped in
Koha::Acquisition::Order->to_api_mapping, so it was exposed verbatim in
the REST API order representation. Since api/v1/swagger/definitions/order.yaml
does not declare this property and sets additionalProperties: false, any
request touching the acquisitions/orders endpoint (e.g. the "Receive
shipments" page) failed with a 500 error: "Properties not allowed:
servicing_instruction".

Exclude it from the API representation, consistent with the other
EDIFACT-only columns (line_item_id, suppliers_reference_number, etc.)
that are already excluded the same way.

Test plan:
1) Go to a vendor page, create a basket, add an order line, and close
   the basket.
2) From the vendor page, click "Receive shipments".
3) Without this patch, the orders table fails to load with a 500 error
   mentioning "Properties not allowed: servicing_instruction".
4) With this patch applied, the orders table loads correctly.
5) prove t/db_dependent/api/v1/acquisitions_orders.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:28 +00:00
Martin Renvoize 2e1f8b8660
Bug 30144: (QA follow-up) Make servicing instruction UI strings translatable
The servicing instructions widget added new user-facing strings (alerts,
button labels, hints) built entirely in JS via string concatenation, none
of which were wrapped for translation - unlike the pre-existing JS in the
same file, which already uses _() throughout. Wrap them the same way,
using _("...").format(...) for the strings that interpolate a value,
matching the existing pattern used elsewhere in this file.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:27 +00:00
Martin Renvoize bbb7f406d2
Bug 30144: (QA follow-up) Fix stored XSS in servicing instructions
The RM's review found two stored XSS vectors in the servicing
instruction free-text (LVT) handling:

1. neworderempty.tt embedded the servicing instruction JSON (and the
   EDIFACT_SI authorised values list) directly into an inline <script>
   block via JSON.parse('[% ... | $raw %]'). TT's html filter does not
   escape single quotes, so a free-text value like
   test' + alert(1) + ' broke out of the JS string literal and
   executed on the next page load of the edit form.

2. basket.pl built the "Servicing instruction" column display by
   concatenating raw LVC/LVT values with '<br>' and rendering the
   result with | $raw in basket.tt, so HTML/script content in a
   free-text value executed directly on the basket page.

Fix the first by moving the JSON into existing/new hidden input
value="" attributes (escaped with the html filter, which is safe for
double-quoted HTML attributes) and reading them back via jQuery's
.val() before JSON.parse(), instead of interpolating untrusted data
into a JS string literal.

Fix the second by passing the display parts to the template as a list
and escaping each with the html filter at render time, instead of
building a pre-escaped raw HTML string in Perl.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:26 +00:00
Martin Renvoize 42ae1e0bf0
Bug 30144: (QA follow-up) Add EDIFACT_SI authorised values to installer data
Tests were failing on upgrade route because they tried to create
EDIFACT_SI authorised values that already existed from the atomicupdate
script, causing unique constraint violations.

This patch:
- Removes test code that created EDIFACT_SI values (tests should assume
  database defaults exist)
- Adds EDIFACT_SI category to auth_val_cat.sql for fresh installs
- Adds all 32 EDItEUR List 3B servicing instruction codes to
  auth_values.yml for fresh installs

Now the authorised values exist in both installation paths:
- Upgrade route: Created by atomicupdate script
- Fresh install: Created by installer SQL/YAML files

Test plan:
1. prove t/db_dependent/Koha/EDI.t
2. prove t/db_dependent/Koha/Edifact/Order.t
3. Verify both tests pass without warnings

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:25 +00:00
Martin Renvoize 711bb2dd76
Bug 30144: Add tests for EDIFACT servicing instruction handling
This commit adds comprehensive tests for servicing instruction
processing in both incoming quotes and outgoing orders:

1. Integration tests for incoming quote processing (Koha/EDI.pm)
   - Tests LVC (coded) and LVT (freetext) extraction
   - Tests multiple GIR occurrences with different servicing instructions
   - Tests all combinations: LVC only, LVT only, and LVC+LVT together

2. Test EDIFACT quote file (QUOTES_SERVICING.CEQ)
   - Contains 3 line items with 4 total orders
   - Demonstrates different servicing instruction patterns

3. Fix for multi-occurrence servicing instruction extraction
   - Modified quote_item() to extract servicing instructions
     per GIR occurrence instead of across all occurrences
   - Ensures each order gets only its own servicing instructions
   - Aligns with existing multi-item processing for funds and stock rotation

Test coverage:
- t/db_dependent/Koha/EDI.t: 29 tests for incoming quote processing
- t/db_dependent/Koha/Edifact/Order.t: 18 tests for outgoing order generation
  (previously added)

All tests pass successfully.

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:24 +00:00
Martin Renvoize b02cd5e1fe
Bug 30144: Add unit tests for servicing instructions
This patch adds comprehensive unit tests for the servicing instruction
functionality in t/db_dependent/Koha/Edifact/Order.t.

Changes:
- Updated test count from 6 to 7
- Added servicing_instruction => undef to test orders to prevent
  TestBuilder from generating random invalid JSON
- Added servicing instruction subtest with 18 tests covering:
  * Single LVC instruction
  * Single LVT instruction
  * Multiple LVC in one group (tests GIR segment overflow)
  * Mixed LVC/LVT in one group
  * Multiple groups
  * Empty servicing instruction
  * Invalid JSON handling (logged to EDI log)
- Tests correctly handle GIR segment 5-element limit and overflow
  to additional segments

Test plan:
1. Run: prove t/db_dependent/Koha/Edifact/Order.t
2. Verify all tests pass

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:23 +00:00
Martin Renvoize c215f2dd43
Bug 30144: Add UI for servicing instructions
This patch adds the user interface for managing and displaying
EDIFACT servicing instructions in the acquisitions module.

Changes to acqui/neworderempty.pl:
- Parses servicing_instruction JSON for editing existing orders
- Fetches EDIFACT_SI authorized values for dropdown
- Passes data to template in both array and JSON formats

Changes to acqui/addorder.pl:
- Accepts servicing_instruction_groups_json from form submission
- Validates and stores JSON in database

Changes to acqui/basket.pl:
- Fetches EDIFACT_SI authorized values
- Formats servicing instructions for display in get_order_infos()
- Creates human-readable display strings with:
  * LVC codes replaced with descriptions
  * Groups separated by <br> tags
  * Format: "Description" or "Description: free text"

Changes to neworderempty.tt:
- Dynamic UI for adding/removing servicing instruction groups
- Each group can contain:
  * One LVC (coded) instruction only, OR
  * One LVT (free-text) instruction only, OR
  * One LVC + one LVT combination
- Validation prevents:
  * More than 2 instructions per group
  * Duplicate LVC or LVT in same group
- LVC instructions use dropdown populated from EDIFACT_SI authorized values
- LVT instructions use free-text input
- Groups serialized to JSON hidden field for form submission
- Smart button labels show what can be added to each group

Changes to basket.tt:
- Displays servicing instructions in basket table
- Pre-formatted in Perl (no JavaScript parsing needed)
- Shows LVC descriptions instead of codes
- Multiple groups displayed on separate lines

Changes to columns_settings.yml:
- Adds servicing_instruction column to basket table configuration

Display format examples:
- LVC only: "Barcode labelling"
- LVT only: "Handle with care"
- Combined: "Binding: Apply to spine"
- Multiple groups (on separate lines):
  Barcode labelling
  Binding: Special instructions

Test plan:
1. Create/edit order
2. Add servicing instruction groups
3. Try to add more than 2 instructions to a group (should prevent)
4. Try to add duplicate LVC or LVT to group (should prevent)
5. Save order
6. View basket and verify instructions display correctly
7. Edit order and verify instructions load into form

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:22 +00:00
Martin Renvoize 4f2829c639
Bug 30144: Process and send servicing instructions in EDI messages
This patch updates the EDIFACT processing to handle multiple servicing
instruction groups in both incoming quotes and outgoing orders.

Changes to Koha/EDI.pm (quote_item):
- Reads both LVC (coded) and LVT (freetext) instructions from incoming quotes
- Validates LVC codes against EDIFACT_SI authorized values
- Stores instructions as JSON array structure
- Groups instructions that work together

Changes to Koha/Edifact/Order.pm (gir_segments):
- Parses JSON servicing instruction data from order
- Generates multiple GIR segments for outgoing orders
- Each instruction becomes a separate GIR element
- Logs JSON parse errors to EDI log using Koha::Logger
- Fixed element counter reset bug when segments exceed 5 elements

Structure allows vendors to specify:
- Single coded instruction: [[{type:"LVC",value:"BB"}]]
- Single text instruction: [[{type:"LVT",value:"Handle with care"}]]
- Combined instructions: [[{type:"LVC",value:"BB"},{type:"LVT",value:"Apply on spine"}]]
- Multiple services: [[{type:"LVC",value:"BB"}],[{type:"LVC",value:"BI"}]]

Test plan:
1. Receive EDIFACT quote with servicing instructions
2. Verify instructions are stored correctly in aqorders
3. Create EDI order from basket
4. Verify GIR segments are generated correctly in outgoing message
5. Check EDI log for any JSON parse errors

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:21 +00:00
Martin Renvoize 8d1e208a7b
Bug 30144: Update DBIC schema
Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:20 +00:00
Martin Renvoize b78a308e60
Bug 30144: Add servicing_instruction field to aqorders table
This patch adds a new TEXT field 'servicing_instruction' to the aqorders
table to store EDIFACT servicing instructions (GIR LVT/LVC segments) as JSON.

The field stores an array of instruction groups, where each group is an
array of instruction objects with 'type' (LVC or LVT) and 'value' fields.

Structure: [[{type:"LVC",value:"BB"},{type:"LVT",value:"text"}],[...]]

This allows for:
- Multiple servicing instruction groups per order line
- Combinations of coded (LVC) and free-text (LVT) instructions
- Instructions that work together in groups

Also adds EDIFACT_SI authorized value category with EDItEUR List 3B
servicing instruction codes for coded (LVC) instructions.

Test plan:
1. Run database update
2. Verify aqorders.servicing_instruction field exists
3. Verify EDIFACT_SI authorized value category is created
4. Verify authorized values include codes like BB, BI, etc.

Sponsored-by: Martin Renvoize <martin.renvoize@gmail.com>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-28 16:36:19 +00:00
Amit Gupta 561ef2f025
Bug 42904: Prevent XSS in patron restriction comments
Test plan

1. Search for a patron and open the patron details page.
2. Under "Patron restrictions", add a manual restriction with the following comment:
   <img src=x onerror="alert(document.domain)">
3. Save the restriction.
4. Navigate to the patron checkouts page:
   /cgi-bin/koha/circ/circulation.pl?borrowernumber=xxx
   Verify that the JavaScript payload executes.
5. Edit the patron record:
   /cgi-bin/koha/members/memberentry.pl?op=edit_form&destination=circ&borrowernumber=xxx
   Verify that the JavaScript payload executes.
6. For OPAC - Go to OPAC Verify that the JavaScript payload executes - /cgi-bin/koha/opac-user.pl
7. Apply the patch.
8. Repeat steps 4, 5 and 6.
9. Verify that:
   1. No JavaScript is executed.
   2. The page loads normally without any alert dialog. Applicable for both the staff and opac.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-26 09:23:34 +00:00
Martin Renvoize d32ae7f064
Bug 42800: (QA follow-up) Fix backslash breaking the shelf list script block
erm.tt built the logged_in_user_lists JS array by manually
interpolating each shelf's id/name into a JS string literal, escaped
with the TT html filter:

    id: "[% shelf.shelfnumber | html %]",
    name: "[% shelf.shelfname | html %]",

The html filter escapes <, >, &, and " -- enough to stop the
<script>-tag and </script>-breakout scenario from the original bug --
but it does not escape backslashes. A list name ending in a backslash
(e.g. "foo\") escapes the closing quote of the JS string literal,
unbalancing it and throwing a JS syntax error that breaks the whole
inline <script> block on every ERM page (including the unrelated
`const logged_in_user = ...` a few lines below), reproducing the
"broken UI" half of the original bug via a different character.

Build the id/name pairs as an actual array of hashes and JSON-encode
them with To.json(), after escaping each value with the html
vmethod. JSON encoding (via the JSON module) correctly escapes
backslashes and quotes for use inside a JS string, while keeping the
values HTML-escaped so they stay inert if ever written into the DOM
via innerHTML (as the DataTables render callback in
EHoldingsLocalTitlesFormImport.vue does).

Test plan:
1. Enable the ERMModule system preference.
2. Create a list named: backslash test \
3. Go to /cgi-bin/koha/erm/eholdings/local/titles/import (or any
   other ERM page).
   => Without this patch: the page's inline script throws a syntax
      error ("Invalid or unexpected token") and
      "logged_in_user_lists is not defined", breaking the Vue
      component.
   => With this patch: the page loads without errors and the list is
      shown correctly in the table.
4. Also verify the original bug's script-tag scenario still works:
   create a list named <script>alert("booh!");</script> and confirm
   it displays as inert text, not executing, on the same pages.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-26 09:23:33 +00:00
Jonathan Druart 22a6ab9729
Bug 42800: Fix XSS in shelf list in the ERM module
const logged_in_user_lists = [% To.json(logged_in_user.virtualshelves.unblessed) | $raw %];

If you have a script tag in a list name the UI will be broken, and there
is potentially a XSS vulnerability

Test plan:
Create a new list with name="<script>alert("booh!");</script>"
Go to /cgi-bin/koha/erm/eholdings/local/titles/import
or any ERM views
=> Without this patch the UI is broken
=> With this patch applied it's not and the table is correctly populated
with the lists

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-26 09:23:32 +00:00
Sanjar Tulkinov Anvar o'g'li 074e0b7261
Bug 42736: Prevent SQL injection in reports/cat_issues_top.pl
The "Most-circulated items" report built its SQL by concatenating the
Criteria and Filter request parameters directly into the query, with no
bind placeholders and no whitelist on the Criteria identifier. An
authenticated staff user with the reports permission could inject
arbitrary SQL and read any data reachable by the Koha database user.

This validates the Criteria parameter against a fixed allow-list of the
report's own column options (matching reports/cat_issues_top.tt) so it
is safe to use as a SQL identifier, and binds every Filter value with a
placeholder in both the column-header query and the main calculation
query. The Limit parameter is validated as a plain integer.

This mirrors the fix already applied to the sibling
reports/bor_issues_top.pl (bug 42369).

Test plan:
0. Apply the patch
1. koha-plack --restart kohadev
2. Go to Reports > Most-circulated items (reports/cat_issues_top.pl)
3. Run the report with various Criteria and Filter values, confirm the
   output is correct and no errors appear
4. Before the patch, a Filter value or the Criteria containing SQL (a
   single quote followed by a subquery) broke out of the query. After
   the patch, confirm such input is treated as a literal value: you get
   normal output or an empty result, with no SQL error, no delay, and no
   data leak

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-26 09:23:29 +00:00
Jonathan Druart e8137a2c94
Bug 42322: Add Cypress tests
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:46 +00:00
Owen Leonard fc8f56b88f
Bug 42322: (follow-up) Correct markup of ILL sidebar menu
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:45 +00:00
Owen Leonard 71d8564789
Bug 42322: (follow-up) Switch to mobile-first handling of sidebar toggle
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:44 +00:00
Jonathan Druart c407f73309
Bug 42322: Hide collapsed sidebar on small screens
When the sidebar is collapsed and the screen is resized to <= 1000px,
'#sidebar-container' should be hidden

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:43 +00:00
Jonathan Druart 7fb0345f7f
Bug 42322: Correctly toggle aria-expanded
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:42 +00:00
Jonathan Druart e8c7901788
Bug 42322: Add missing raw filter for aside_block
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:41 +00:00
Jonathan Druart 4d87ae3424
Bug 42322: Hide .patroninfo, .browse-controls and #search-facets when collapsed
Fix sidebar when collapsed on:
 * /members/moremember.pl?borrowernumber=42
 * /catalogue/search.pl?advsearch=1&idx=kw&q=d&idx=kw&q=&idx=kw&q=&weight_search_submitted=1&weight_search=1&sort_by=relevance
 * /catalogue/detail.pl?biblionumber=438&searchid=scs_1776086577193
   (with BrowseResultSelection ON)

Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:40 +00:00
Owen Leonard 34644298e4
Bug 42322: (follow-up) A version with just the button
Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:39 +00:00
Owen Leonard f9dc282e26
Bug 42322: (follow-up) Proposed alternative style
This version keeps a visual indicator of the collapsed sidebar.

I've also added handling of the "aria-expanded" attribute.

Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:38 +00:00
Jonathan Druart 4784d952e7
Bug 42322: Vue apps
Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:37 +00:00
Owen Leonard b804854865
Bug 42322: Auto tidy
To ease rebase!

Re-run.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:36 +00:00
Jonathan Druart be9a2c7c5f
Bug 42322: Use main-container.inc from reports
Pass the aside block from the report module

Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:35 +00:00
Jonathan Druart add690d0f4
Bug 42322: Pass the aside to main-container
Improve main-container.inc so we can pass a specific aside block

Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:34 +00:00
Jonathan Druart ab7602b0f3
Bug 42322: Add a 'collapse sidebar' button
On bug 42190 we discuss the possibility to collapse the sidebar in order to enlarge the screen and specifically the table in the main div.

This patch adds the button to main-container.inc that is supposed to be
used from everywhere.

Signed-off-by: Clemens Tubach <clemens.tubach@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:33 +00:00
Matt Blenkinsop ce3643073a
Bug 39959: Add a total column to the invoices table
Test plan:
1. Create an order and receive it to create an invoice
2. In the invoices search, look for your invoice
3. There will be no total against the invoice
4. Apply patch and restart_all
5. Refresh the page, the total column should now be present

Signed-off-by: Coleen Lilly <clilly@aacpl.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:32 +00:00
Matt Blenkinsop 2ce97a58e2
Bug 12278: (follow-up) Use invoice quantity to sum total
Prevents additional orders from subscriptions from being swallowed into the invoice total calculation

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:31 +00:00
Matt Blenkinsop f7bbdd4976
Bug 12278: Add unit tests
Signed-off-by: Coleen Lilly <clilly@aacpl.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Sponsored-by: Anne Arundel County Public Library <https://www.aacpl.net/>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:30 +00:00
Matt Blenkinsop b114eb709f
Bug 12278: Add a total column to the table in parcels.pl
Test plan:
1. Create an order with multiple items and receive the order in full
2. From the vendor record, click Receive shipments
3. You should be in parcels.pl and the table should show your shipments
4. There will be no column displaying the total
5. Apply patch and restart_all
6. Refresh the page, the column should now be visible showing the total

Signed-off-by: Coleen Lilly <clilly@aacpl.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:29 +00:00
Olivia Reynolds e886c3a7e0
Bug 39337: (follow-up) Default to not suppressing OAI records
In the case when a biblio is deleted, we should not suppress its
orphaned records. Therefore, invert the conditions in the WHERE clause
to not suppress the record if no biblio could be found.

Test plan:
1.  Enable the system preferences "OAI-PMH" and "OpacSuppression".
    Ensure "OpacSuppressionByIPRange" is blank.
2.  Navigate to
    /cgi-bin/koha/oai.pl?verb=ListRecords&metadataPrefix=oai_dc
3.  Select a displayed record, and delete its corresponding biblio.
4.  Refresh the OAI page, verify the record has vanished.
5.  Apply patch.
6.  Refresh the OAI page, verify the record now appears and is marked as
    deleted. It may have moved to the top of the list.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:28 +00:00
Olivia Reynolds a03c119ccd
Bug 39337: (follow-up) Explicitly name tables
As we're aliasing the primary tables as `main`, we need to use that name
to refer to their columns.

Test plan:
1.  Apply patch.
2.  Prove t/db_dependent/OAI/Server.t and verify no tests fail.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:27 +00:00
Olivia Reynolds 091f0d7a89
Bug 39337: Filter out suppressed OAI records in SQL
Rather than fetching each biblio before choosing to suppress it, this
patch performs this filtering in the initial SQL query that fetches the
biblionumbers.

Note: for queries including items, this additional filtering is
performed in each of the three subqueries, necessitating three
quasi-joins against biblio. If we were to hoist this filtering to the
outer layer, we would also need to remove the LIMIT from each subquery
so as to allow each of them to be fairly weighted. As the preceding
comment implies keeping the subqueries separate has been beneficial for
performance, I've elected not to hoist the WHERE.

Test plan (specific URLs given for KTD, with thanks to David Cook):

1.  Apply patch.
2.  Navigate to http://localhost:8081/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=2
3.  Set 942$n to "Yes" and save the record.
4.  Disable the system preference "OpacSuppression", and ensure
    "OpacSuppressionByIPRange" is blank.

5a. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/oai.pl?verb=GetRecord&metadataPrefix=oai_dc&identifier=KOHA-OAI-TEST:2
5b. Verify KOHA-OAI-TEST-2 appears on
    http://localhost:8080/cgi-bin/koha/oai.pl?verb=ListRecords&metadataPrefix=oai_dc

6.  Enable the system preference "OpacSuppression", and ensure
    "OpacSuppressionByIPRange" is blank.
7.  Repeat steps 5a-5b, verifying that:
    - the OAI GetRecord page returns "There is no biblio record with
      this identifier".
    - the OAI ListRecords page does not show KOHA-OAI-TEST-2.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:26 +00:00
Olivia Reynolds 4dd0bfc107
Bug 39337: Add is_opac_suppressed helper to C4::Context
Previously, OPAC suppression was applied inconsistently, with only some
callers taking OpacSuppressionByIPRange into account. This patch adds a
new helper to C4::Context to determine whether OPAC suppression should
be active for this request, and switches to using it everywhere that was
previously manually checking the system preferences.

Test plan (specific URLs given for KTD, with thanks to David Cook):

1.  Apply patch.
2.  Navigate to http://localhost:8081/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=2
3.  Set 942$n to "Yes" and save the record.
4.  Disable the system preference "OpacSuppression", and ensure
    "OpacSuppressionByIPRange" is blank.

5a. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/opac-detail.pl?biblionumber=2
5b. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/opac-MARCdetail.pl?biblionumber=2
5c. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/opac-ISBDdetail.pl?biblionumber=2
5d. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/opac-search.pl?idx=&q=The+ideas+behind&weight_search=1
5e. Verify you can successfully access
    http://localhost:8080/api/v1/public/biblios/2
5f. Verify you can successfully access
    http://localhost:8080/cgi-bin/koha/oai.pl?verb=GetRecord&metadataPrefix=oai_dc&identifier=KOHA-OAI-TEST:2
5g. Verify KOHA-OAI-TEST-2 appears on
    http://localhost:8080/cgi-bin/koha/oai.pl?verb=ListRecords&metadataPrefix=oai_dc

6.  Enable the system preference "OpacSuppression", and ensure
    "OpacSuppressionByIPRange" is blank.
7.  Repeat steps 5a-5g, verifying that:
    - the OPAC details pages return "Record blocked".
    - the OPAC search page returns "No results found".
    - the API returns "Bibliographic record not found".
    - the OAI GetRecord and ListRecords show "Record Status: deleted"
      for KOHA-OAI-TEST-2.
8.  Keep "OpacSuppression" enabled, but set "OpacSuppressionByIPRange"
    to "." (to allow all IPs to bypass suppression).
9.  Repeat steps 5a-5g, verifying that every page is successful again.
10. Run `prove t/Context.t`, and verify all tests are successful.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:25 +00:00
Jonathan Druart 8316c01bac
Bug 41706: Add a test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Amended:
-    $marc->append_fields( MARC::Field->new( '100', ' ', ' ', a => 'Aristotle.' ) );
-    marc2cites($marc);
+    $marc_record->append_fields( MARC::Field->new( '100', ' ', ' ', a => 'Aristotle.' ) );
+    marc2cites($marc_record);

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:24 +00:00
Roman Dolny b60b7c3a3f
Bug 41706: "Use of uninitialized value..." warning in C4/Record.pm
The warning:
[WARN] Use of uninitialized value $2 in split
    at /kohadevbox/koha/C4/Record.pm line 924.
appears in the plack-opac-error.log file.
It happens during the preparation of data for the "Cite" feature
in OPAC for some of author's fields.

To test:
========
1. Observe plack-opac-error.log.
2. In OPAC go to record with one word author like Aristophanes,
   Aristotle or Xenophon (biblionumbers: 64, 176, 254).
3. Show it in any of "detail" view (Normal, MARC, ISBD).
4. Warning appears in plack-opac-error.log.
5. Apply the patch; restart_all.
6. Repeat 2-3. Warning doesn't appear.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-25 08:56:20 +00:00
Paul Derscheid 138d2b64ed
Bug 42207: Allow library embed on GET /patrons/{patron_id}
- Add 'library' to the allowed x-koha-embed values for the
  GET /patrons/{patron_id} endpoint
- Add unit test verifying the library embed returns library_id
  and name

Test plan:
1. Apply patch
2. yarn api:bundle
3. prove t/db_dependent/api/v1/patrons.t
4. Verify all tests pass, including the new 'library embed'
   subtest under get() which verifies that requesting
   x-koha-embed: library returns the patron's library object

Sponsored-by: Büchereizentrale Schleswig-Holstein <https://www.bz-sh.de/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 15:58:25 +00:00
Olivia Reynolds ad30c815ce
Bug 34166: Add total column to suggestions table
This patch displays the "total_price" field from the API in the
acquisition suggestions table. In line with other totals, it is
displayed unitless after currency conversion.

Test plan:
1.  Navigate to Acquisitions > Suggestions.
2.  Click "New purchase suggestion", then add a title, price, and
    number of copies.
3.  Make note of the calculated total price.
3.  Save the suggestion to return to the table.
4.  Verify there is no "Total" column on the suggestions table.
5.  Apply patch, update database, and refresh.
6.  Verify there is a "Total" column on the suggestions table that
    matches the previously displayed total price.
7.  Verify the "Total" column is listed under the "Columns" dropdown.
8.  Above the suggestions table, click "Configure".
9.  In the column settings for suggestions, check both "Is hidden by
    default" and "Cannot be toggled" for "total".
10. Underneath the column settings for suggestions, uncheck "Save
    configuration state on page change".
11. Save the column settings.
12. Return to the suggestions table.
13. Verify the "Total" column is hidden.
14. Verify the "Total" column is not listed under the "Columns"
    dropdown.

Sponsored-by: Open Fifth
Signed-off-by: Michaela <michaela.sieber@kit.edu>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 15:58:24 +00:00
Owen Leonard 89066b3c7b
Bug 42111: Update various admin pages to use grid layout for forms - Part 1
This patch updates several administration templates with new markup and
CSS for layout forms using CSS grids.

To test, apply the patch rebuild the staff interface CSS.

Check the following pages, confirming that the "New" and
"Edit" versions of the form look correct in each case:

- Administration -> Additional fields
- Administration -> Audio alerts
- Administration -> Cities and Towns
- Administration -> Classification configuration
  -> Classification sources
  -> Classification filing rules
  -> Classification splitting rules

On the classification splitting rules page I've reformatted the regular
expression section to fit better. To test, add or edit a splitting rule.

- Under "Splitting routine" choose "RegEx"
  - "Regular expression" and "Test the regular expressions" fields
    should appear.
- Add one or more regular expressions, e.g.

  s/\s/\n/g
  s/(\s?=)/\n=/g
  s/^(J|K)\n/$1 /

- In the "Test the regular expressions" text area enter some call
  numbers e.g.

  830 Han
  159.9 (091) Gesh
  J 3 Kin =774
  830 Hil =774 4
  830 Las=20 4

- Click "Test"
- The formatted call numbers should appear below in a grid:

+---------+------------------+--------------+----------------+--------------+
| 830 Han | 159.9 (091) Gesh | J 3 Kin =774 | 830 Hil =774 4 | 830 Las=20 4 |
+---------+------------------+--------------+----------------+--------------+
| 830     | 159.9            | J 3          | 830            | 830          |
| Han     | (091)            | Kin          | Hil            | Las          |
|         | Gesh             | =774         | =774           | =20          |
|         |                  |              | 4              | 4            |
+---------+------------------+--------------+----------------+--------------+

Sponsored-by: Athens County Public Libraries
Signed-off-by: Felicie <felicie.thiery@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 15:58:23 +00:00
Katrin Fischer 7de8e290b9
Bug 35061: Fix documentation for -pf|preserve-field in import_patrons.pl
There was a mismatch between the documentation and the shortcut used
in the code. This patch fixes the documentation so it matches the code:

'pf|preserve-field=s'            => \@preserve_fields,

=item B<-k|--preserve-field>

Prevent specified patron fields for existing patrons from being overwritten

So pf is the right shortcut.

Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 15:58:20 +00:00
Tomás Cohen Arazi d6e40f612c
Bug 20846: Filter suppressed records from public lists in OPAC
When OpacSuppression is enabled, records marked as suppressed
(biblio.opac_suppressed = 1) still appear in public lists viewed
through the OPAC. This is confusing for patrons.

This patch adds a filter_by_not_suppressed method to
Koha::Virtualshelfcontents and uses it in opac-shelves.pl to
exclude suppressed records from list display when OpacSuppression
is enabled. OpacSuppressionByIPRange is respected: users within
the allowed IP range still see all records.

Test plan:
1. Apply patch
2. Enable OpacSuppression system preference
3. Create a public list and add some titles to it
4. Suppress one of the titles (set 942$n = 1)
5. View the list in the OPAC
=> SUCCESS: Suppressed record is no longer shown in the list
6. Set OpacSuppressionByIPRange to match your IP (e.g. 127.)
7. Repeat step 5
=> SUCCESS: All records (including suppressed) are shown
8. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Virtualshelves.t
=> SUCCESS: Tests pass!
9. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 14:31:50 +00:00
Olivia Reynolds ff2b951c4d
Bug 41648: Validate plugin date metadata
If a plugin reports a date_authored or date_updated we can't parse,
remove the invalid property from the metadata hashref and emit a
warning.

To test this, introduce a separate test plugin directory and test file.
Having a plugin that produces warnings in the main test directory would
require accounting for this warning in every existing test that loads
plugins. Correspondingly, this patch also fixes an invalid date in an
existing test plugin.

Test plan:
1. Apply patch.
2. Run `prove -r t/db_dependent/Koha/Plugins/` and verify there are no
   test failures.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 14:31:48 +00:00
Olivia Reynolds fd24968eb2
Bug 41648: Handle parse errors in KohaDates
If dt_from_string throws an exception whilst trying to parse the input,
catch it and return an empty result.

Test plan:
1. Apply patch.
2. Prove t/db_dependent/Template/Plugin/KohaDates.t

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 14:31:47 +00:00
Matt Blenkinsop dca7313511
Bug 43203: Correctly rollback test data
Test plan:
1) In the database, verify how many biblios exist (select count(*) from biblio;)
2) Run prove t/db_dependent/Koha/Item.t
3) Check the count again, there should now be 3 extra bib records left from the test run
4) Apply patch
5) Repeat the process, the biblio count should never grow after test runs

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 14:31:46 +00:00
Andrew Fuerste Henry fbfccfdbf2
Bug 43331: Add ccode class to holdings table
To test:
1 - go to a bib record with items
2 - inspect the items table, confirm there is no class on the cell
    containing the ccode
3 - Apply patch, restart_all
4 - reload your bib page
5 - inspect the table again, confirm the cell has the class "ccode"

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

Signed-off-by: Michelle Spinney <mspinney@clamsnet.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 14:31:44 +00:00
Fridolin Somers 634193fc6a
Bug 43339: automatic_item_modification_by_age_configuration is not a real system preference
It should not fail the unit test t/db_dependent/check_sysprefs.t.

Test plan :
1.0) Without patch
1.1) Run : prove t/db_dependent/check_sysprefs.t
=> 'All tests successful'
1.2) Go to Cataloging > Automatic item modifications by age
1.3) Create some entries and save
1.4) Run : prove t/db_dependent/check_sysprefs.t
=>
    #   Failed test at t/db_dependent/check_sysprefs.t line 96.
    #          got: '1'
    #     expected: '0'
    # Too many sysprefs in DB: automatic_item_modification_by_age_configuration
2.0) Apply patch
2.1) Run : prove t/db_dependent/check_sysprefs.t
=> 'All tests successful'

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 14:31:43 +00:00
Andrew Fuerste Henry b33bb4b380
Bug 43342: Remove line due to template toolkit
To test:
1 - go to the OVERDUES_SLIP notice and confirm you are using the default content
2 - have or create a patron with an overdue item
3 - click Print and then Print overdues
4 - confirm there are two empty lines between the first and second lines of text
APPLY PATCH, restart_all
5 - reload your OVERDUES_SLIP notice page
6 - click View default
7 - confirm the FOREACH statement now starts with "[%-"
8 - click Copy to Template, save your changes
9 - print your patron's overdue slip again, confirm you have only one blank line

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-21 14:31:40 +00:00
Hammat Wele 35b58d65c5
Bug 43211: (follow-up) fix 'barcode is not defined' error
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-20 16:10:44 +00:00
Janusz Kaczmarek a17d27f8ae
Bug 43211: Koha hangs when receiving order with AcqCreateItem set to 'receiving an order'
Koha hangs when receiving an order with AcqCreateItem set to
"receiving an order".

Test plan:

1. Create a basket and set "Create items when" to "receiving an order".
2. Add an order and close the basket.
3. Attempt to receive the order. Koha will most likely hang.
4. Apply the patch; restart_all.
5. Repeat p. 3. You should be able co proceed.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-20 16:10:38 +00:00
Tomás Cohen Arazi 3522dc2bca
Bug 42567: Remove C4::Koha::GetNormalizedEAN in favor of MetadataExtractor
Adds get_normalized_ean to the flavor-specific extractors:
- MARC21: 024$a with indicator 1 = 3
- UNIMARC: 073$a

Exposes it as $biblio->normalized_ean and migrates all callers.

Assisted-by: Sonnet 4.6 (Anthropic)

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Koha.t \
          t/db_dependent/Koha/Biblio.t \
          t/db_dependent/Koha/Biblio/Metadata/Extractor/MARC/MARC21.t \
          t/db_dependent/Koha/Biblio/Metadata/Extractor/MARC/UNIMARC.t
=> SUCCESS: Tests pass
3. Verify EAN numbers still display in OPAC and staff detail pages
4. Sign off :-D

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-20 15:34:10 +00:00
Tomás Cohen Arazi 3d3f625b56
Bug 42566: Remove C4::Koha::GetNormalizedUPC in favor of MetadataExtractor
GetNormalizedUPC duplicated logic already available via the
MetadataExtractor (Bug 34828) and exposed as $biblio->normalized_upc.

All callers migrated:
- Scripts with $biblio available: use $biblio->normalized_upc
- C4::Search (only MARC::Record): reuses the extractor instance
  already created for OCLC

Assisted-by: Sonnet 4.6 (Anthropic)

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Koha.t \
          t/db_dependent/Koha/Biblio.t
=> SUCCESS: Tests pass
3. Verify UPC numbers still display in OPAC and staff detail pages
   (Syndetics cover images)
4. Sign off :-D

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-20 15:34:08 +00:00
Tomás Cohen Arazi 5fb7fd5f2d
Bug 35142: Remove GetNormalizedOCLCNumber, oclc_number, strip_orgcode
All callers migrated. Removes dead code:

- C4::Koha::GetNormalizedOCLCNumber
- Koha::Util::MARC::oclc_number
- Koha::Util::MARC::strip_orgcode
- Koha::Util::MARC::OCLC_REGEX constant

Test plan:
   $ prove t/Koha/Util/MARC.t t/db_dependent/Koha.t

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Assisted-by: Sonnet 4.6 (Anthropic)
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-20 15:34:07 +00:00
Tomás Cohen Arazi 263a1bd536
Bug 35142: Migrate callers to use metadata extractor
Replaces all calls to GetNormalizedOCLCNumber() and
Koha::Util::MARC::oclc_number() with the extractor pattern.

OPAC scripts pass their already ViewPolicy-filtered $record directly
to the extractor. This preserves the existing filtering behavior
without introducing new abstraction layers:

    Koha::Biblio::Metadata::Extractor->new( { metadata => $record } )->get_normalized_oclc

Intranet scripts and the Koha::Biblio convenience method use the
raw cached extractor (035 visibility is not affected by ViewPolicy
in practice, and intranet doesn't filter):

    $biblio->normalized_oclc

Test plan:
1. Verify OCLC numbers display on OPAC detail, ISBD, MARC detail
2. Verify staff catalogue detail and search results
3. prove t/db_dependent/Koha/Biblio.t

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Assisted-by: Sonnet 4.6 (Anthropic)
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-20 15:34:06 +00:00
Tomás Cohen Arazi 6b77f28dd7
Bug 35142: Move get_normalized_oclc to base MARC extractor
Moves get_normalized_oclc from the MARC21-specific extractor to the
base ::MARC class (035$a is flavor-independent) with improved matching:

- Case-insensitive OCoLC detection (matches ocolc, OCOLC, etc.)
- Generic orgcode prefix stripping (any parenthesized prefix, not
  just literal "(OCoLC)") for resilience against cataloging typos

Test plan:
   $ prove t/db_dependent/Koha/Biblio/Metadata/Extractor/MARC/MARC21.t

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Assisted-by: Sonnet 4.6 (Anthropic)
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-20 15:34:02 +00:00
Pedro Amorim 4dae704301
Bug 41029: DBRev 26.06.00.016
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 16:07:59 +00:00
Martin Renvoize 575e90f88f
Bug 41029: (QA follow-up) Seed add_to_basket record source and tidy atomicupdate
Without a matching entry in the record_sources table, the new
"Add to basket" option never appears in the Source filter dropdown
of the marc-overlay-rules admin page for module=source, since that
dropdown is populated from Koha::RecordSources, not the template's
label switch. Seed it in both the mandatory install-time data and
the upgrade atomicupdate, matching how batchimport/z3950/etc. were
introduced.

Also drop the unused say_warning import, route success messages
through say_success instead of a bare say, and simplify the
selectrow_array calls.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 16:07:58 +00:00
Nick Clemens f473c7d451
Bug 41029: Add option for marc overlay rules to apply when adding to a basket
This patch adds a new option to allow defining overlay rules for 'Add to basket' - such that they
will be used when adding orders in acquisition. Existing 'Staged MARC Import' rules will be copied to
the new fitler in order to preserve behavior

To test:
 1 - Paste the following into MarcFieldsToOrder and save
  price: 975$p
  quantity: 975$q
  budget_code: 975$h
 2 - Paste the following into MarcItemFieldsToOrder and save
  homebranch: 949$a
  holdingbranch: 949$b
  itype: 949$y
  nonpublic_note: 949$x
  public_note: 949$z
  loc: 949$c
  ccode: 949$8
  notforloan: 949$7
  uri: 949$u
  copyno: 949$t
  price: 949$g
  replacementprice: 949$v
  itemcallnumber: 949$o
  quantity: 949$k
  budget_code: 949$l
 3 - Go to Admin -> Record overlay rules
 4 - Define 2 rules:
  Source - Staged Marc Import - 949 - Add and append (the rest of the options are set when add and append is selected
  Source - Staged Marc Import - * - Protect (the rest of the options are set when add and append is selected
 5 - Grab test file from bug 39518
 6 - Import the records, ignoring items
 7 - Edit the test file and change some values for the 949 rows
 8 - Go to Acquisitions, find a basket for a vendor, add to basket from a new file
 9 - Stage the file to find matches (already imported above) and to replace
10 - Complete the orders
11 - View the record and confirm the new 949 are added
12 - Edit the 949 in the file again
13 - Apply patch, update database, restart all
14 - Edit overlay rules and remove the 'Staged Marc Import' rules, leaving only the 'Add to basket rules'
15 - Add to basket again using the updated file and confirm new 949 are added
16 - Success!

Signed-off-by: Benjamin Daeuber <BDaeuber@fargolibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 16:07:57 +00:00
John Doe 9fc9f84b8d
Bug 28740: (follow-up) Add missing C4::Log import for cronlogaction
The patch added cronlogaction() calls but did not import the function.
This caused a runtime error: Undefined subroutine &main::cronlogaction.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ perl misc/cronjobs/cleanup_database.pl --illrequests-days 30 \
       --illrequests-status COMP -v
=> SUCCESS: Dry-run works, reports count of requests that would be removed
3. Run with --confirm to actually purge
4. Sign off :-D

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 16:07:56 +00:00
Lari Strand 5ebc4d79a1
Bug 28740: Add option for cleaning up ILL requests
This adds two new parameters:
--illrequests-status
--illrequests-days (optional, defaults to 30 days)

These can be repeated to include specific statuses for cleanup. At least one status must be specified.

To test:
0 - Apply patch
1 - Create several ILL requests with different statuses (e.g., CANCELLED, REJECTED, COMPLETED, PENDING)
2 - Set some requests to be older than 30 days (update their updated timestamp)
3 - Run cleanup_database.pl with --illrequests-status CANCELLED
4 - CANCELLED requests older than 30 days are removed, others remain
5 - Run cleanup_database.pl with --illrequests-status CANCELLED --illrequests-status REJECTED
6 - Both CANCELLED and REJECTED requests older than 30 days are removed, other statuses remain
7 - Run cleanup_database.pl with --illrequests-days 5 --illrequests-status PENDING
8 - PENDING requests older than 5 days are removed
9 - Verify dry-run mode (no --confirm) correctly counts records that would be deleted without deleting them
10 - Run cleanup_database.pl with --illrequests-days without any --illrequests-status
11 - Error message displayed requiring at least one status to be specified

Sponsored-by: Koha-Suomi Oy
Signed-off-by: John Doe <tomascohen@gmail.com>
edit: tidied the file (tcohen)
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 16:07:54 +00:00
Pedro Amorim f2003e3e52
Bug 35104: (RM follow-up) Restore StripNonXmlChars in TransformHtmlToMarc
Removed by 6726b1a254 on the assumption store() strips centrally -
but TransformHtmlToMarc never calls store(), so callers like
addbiblio.pl (FindDuplicate, duplicate redisplay) see unstripped
control characters again, the same failure Bug 34549 fixed.

repair_marcxml covers more faults but works on full MARCXML and can
throw - too big a change here; left as a follow-up.

Test plan:
prove t/db_dependent/Biblio/TransformHtmlToMarc.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 14:34:00 +00:00
Olivia Reynolds dd7777c8b9
Bug 43313: Fix catalogue detail shelving location display
During the tidying up of Bug 41566, av_loc was accidentally changed to
map descriptions to codes, rather than vice-versa. This broke the
formatting of item permanent locations.

Test plan (for KTD):
1.  Navigate to an catalogue detail page, for example
    http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=262
2.  Run the following SQL, replacing 262 with the biblionumber of your
    chosen page:
      UPDATE items SET location='CHILD' SET permanent_location='GEN' WHERE biblionumber=262;
3.  Refresh the page and verify the items' shelving locations display
    "(Children's area)" only.
4.  Apply patch and force refresh.
5.  Verify the shelving location now correctly displays "General
    stacks" and "(Children's area)" on separate lines.

Sponsored-by: Open Fifth
Signed-off-by: John Vinke <jvinke@austinseminary.edu>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 14:33:59 +00:00
Martin Renvoize bad9bb47ae
Bug 43215: preferences.js: Report one save confirmation per preference, not per checkbox option
Checkbox-group system preferences (multiple_sortable, ill_backends, and
the OPAC/staff interface language selectors) render one checkbox per
option. All the checkboxes for a given preference share its 'name'
attribute, but each has its own unique 'id'.

Saving such a preference produced one "Saved preference ..."
confirmation line per checkbox instead of a single line for the
preference as a whole, because KOHA.Preferences.Success() built the
message from each modified element's 'id' rather than its 'name'.

A related issue: the checkbox 'change' handler marked every
'.preference-checkbox' on the whole page as modified, regardless of
which preference it belonged to. This meant editing one checkbox-group
preference silently re-saved (and, before this patch, also reported as
saved) any other checkbox-group preference sharing the same tab - for
example, editing OPACLanguages would also touch the unrelated
StaffInterfaceLanguages preference on the I18N/L10N tab.

This patch:
* Scopes the 'modified' marking to checkboxes sharing the same
  preference name, so unrelated checkbox-group preferences on the same
  tab are left alone.
* Groups the save confirmation message by preference name
  (deduplicated), so each preference produces exactly one "Saved
  preference" line regardless of how many options it has.

Test plan:
1. Go to Administration > System preferences and search for
   OPACAuthorIdentifiersAndInformation.
2. Select two or more identifier types and click Save.
3. Confirm the confirmation banner shows a single "Saved preference
   OPACAuthorIdentifiersAndInformation" line, not one per selected
   identifier.
4. Drag one of the selected items to reorder it, click Save again,
   confirm you still get only one confirmation line, then reload the
   page and confirm the new order was kept.
5. Go to Administration > System preferences > I18N/L10N tab.
6. Toggle one of the checkboxes under "Enable the following languages
   on the OPAC" (OPACLanguages) and click Save.
7. Confirm the confirmation only reports OPACLanguages as saved - the
   staff interface language list (StaffInterfaceLanguages) should not
   appear in the message.
8. Reload the page and confirm the staff interface language selection
   is unchanged.
9. As a sanity check, modify an ordinary non-checkbox preference (e.g.
   a free text or yes/no preference) and confirm it still reports
   exactly one "Saved preference" line, same as before this patch.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 14:33:58 +00:00
Andrew Fuerste Henry 549e0b3068
Bug 42553: (QA Follow-up) tidy preferences.pl
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 14:33:57 +00:00
Martin Renvoize eb93c2b2e7
Bug 42553: preferences.pl: Allow 'multiple_sortable' to use 'authval' source
The 'choices' renderer in admin/preferences.pl already supports a
dynamic 'authval' source: the YAML can say

    choices: authval
    source: SOMECATEGORY

and the choices are populated from the named authorised value category
at render time.

The 'multiple_sortable' renderer (the SortableJS-backed drag-and-drop
multi-select used by OPACAuthorIdentifiersAndInformation and
ArticleRequestsSupportedFormats) had no equivalent — choices had to be
hardcoded in the .pref YAML.

This patch adds the same dispatcher to 'multiple_sortable' so prefs
needing an ordered, picker-driven multi-select of authorised values
can declare:

    multiple_sortable: authval
    source: PAYMENT_TYPE

Behaviour for existing prefs (which pass a hash directly to
'multiple_sortable') is unchanged — the new branch only fires when the
value is a scalar.

Test plan:
1. Create a temporary system preference using the new pattern, e.g. by
   adding to admin/preferences/accounting.pref:
       - pref: TestSortableAV
         multiple_sortable: authval
         source: PAYMENT_TYPE
   and seeding INSERT INTO systempreferences with type='Free'.
2. Visit Administration > System preferences and find the pref.
3. Verify the widget renders as a sortable list of every value in the
   PAYMENT_TYPE authorised value category, with checkboxes and drag
   handles, matching the look of OPACAuthorIdentifiersAndInformation.
4. Pick two or three, reorder them, save.
5. Reload the page and verify the order and selection are preserved.
6. With 'source' omitted or 'multiple_sortable' set to a string other
   than 'authval', verify the page raises a clear error.
7. Existing prefs (OPACAuthorIdentifiersAndInformation,
   ArticleRequestsSupportedFormats) continue to render unchanged.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Debbie McCarthy <debbie.mccarthy@westsussex.gov.uk>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 14:33:56 +00:00
Kyle M Hall 812ac7155d
Bug 41197: Don't require a CSRF token for opac-account-pay-return.pl
This page is intended for redirect/return from external payment vendors - they are not going to have/get a CSRF token from Koha for this.
The payments are handled in a POST to the API, so this should not be a sensitive page, it just provides user confirmation.
Some vendors use only a POST, we should not require CSRF on this page.

Test Plan:
1) Apply the first patch
2) prove t/Koha/Middleware/CSRF.t
3) Note the tests fail!
4) Apply this patch
5) prove t/Koha/Middleware/CSRF.t
6) All tests pass!

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 14:33:55 +00:00
Kyle M Hall 3e87d13e67
Bug 41197: Add unit tests
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 14:33:52 +00:00
Pedro Amorim 22fad96c0d
Bug 35104: (RM follow-up) Fix regressions.t for Metadata's new auto-repair
Koha::Biblio::Metadata->store() now repairs recoverable MARCXML faults
before saving, so this test's invalid data no longer reaches the DB
and detail.pl never hits a decoding error.

Write the metadata directly via the DBIC row instead, bypassing
store()'s repair, so the test still exercises detail.pl's own
defensive handling of corrupt metadata.

Test plan:
prove t/db_dependent/selenium/regressions.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 11:03:34 +00:00
Pedro Amorim 868751b221
Bug 43091: (RM follow-up) Give the OPAC self-renewal island a Pinia store
hydrate() only installs Pinia for an island when its config.stores is
non-empty. patron-self-renewal kept config: {} after islands-opac.ts
was split out, so it never got one.

http-client.js's post() calls submitting(), which needs Pinia's
useMainStore(). With none active, it throws before fetch() runs -
get() skips submitting() by default, so only the actual renewal
submission failed.

Test plan:
prove t/cypress/integration/Islands/SelfRenewal.ts

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 11:03:33 +00:00
Pedro Amorim c4e70ad745
Bug 35797: (RM follow-up): Fix xt/api.t
prove xt/api.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-19 11:03:30 +00:00
Pedro Amorim 0e69e8d3a1
Bug 41996: DBRev 26.06.00.015
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:39 +00:00
Kyle M Hall d9b06c79e3
Bug 41996: (QA follow-up) Fix up atomic update
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:37 +00:00
Kyle M Hall d576dbadcb
Bug 41996: (QA follow-up) Test that invoicenumber is recorded on EDI invoice errors
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:36 +00:00
Kyle M Hall 7e445f1ce6
Bug 41996: (QA follow-up) Update EDI.t for improved invoice error messages
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:35 +00:00
Martin Renvoize e9a442e933
Bug 41996: Filter EDI errors on invoice page by invoicenumber
The invoice page previously fetched all edifact_errors for the EDI
message, causing errors from unrelated invoice lines to appear on every
invoice generated from the same EDI file. For example, one unmatched
ordernumber in a file with 20 invoices would flag all 20 as having
errors.

Now only errors where invoicenumber matches the current invoice (or
where invoicenumber is NULL, indicating a message-level error) are
displayed.

Signed-off-by: Ray Delahunty <lib-systems@arts.ac.uk>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:34 +00:00
Martin Renvoize a3889a7d90
Bug 41996: Thread invoicenumber through EDI invoice error recording
All errors generated during process_invoice and receipt_items now
store the invoicenumber of the specific invoice being processed. This
allows errors to be displayed only on the relevant invoice page rather
than appearing on every invoice generated from the same EDI file.

Also fixes two pre-existing spacing bugs in error message strings
("itemnumberis missing" and "barcode"for invoice line").

Signed-off-by: Ray Delahunty <lib-systems@arts.ac.uk>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:33 +00:00
Martin Renvoize 51e4567ca6
Bug 41996: Schema Updates
Signed-off-by: Ray Delahunty <lib-systems@arts.ac.uk>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:32 +00:00
Martin Renvoize 60cde3718a
Bug 41996: Add invoicenumber column to edifact_errors table
Adds an optional invoicenumber column to edifact_errors so that errors
generated during invoice processing can be associated with the specific
invoice they relate to, rather than only the EDI message as a whole.

Message-level errors (e.g. unmatched vendor EAN) leave invoicenumber
NULL, indicating they apply to the whole file.

Signed-off-by: Ray Delahunty <lib-systems@arts.ac.uk>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:31 +00:00
Martin Renvoize 52d3cd3835
Bug 41996: Improve EDI invoice error messages for branch mismatches
When EDI invoice processing fails to match an item to a branch, the
error message now includes:
- The ordernumber referenced in the invoice (RFF+LI segment)
- The branches that order actually has items at
- A hint that the ordernumber in the invoice may be incorrect

Also adds a warning when an invoice line references an order that is
already marked as complete, which can indicate the supplier sent the
wrong ordernumber.

These improvements help acquisitions staff and vendors quickly diagnose
whether the problem is a supplier error (wrong RFF+LI value) rather
than a Koha data issue.

Signed-off-by: Ray Delahunty <lib-systems@arts.ac.uk>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:30 +00:00
Kyle M Hall 9e8075eaaf
Bug 42789: (QA follow-up) Add test for cancelling an order with a held item
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:29 +00:00
Kyle M Hall 220b09c4e0
Bug 42789: (QA follow-up) Log item deletion failures during EDI cancellation
This patch logs any object messages returned by cancel() to the EDI log,
including the itemnumber if available.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:28 +00:00
Martin Renvoize 45a60d487c
Bug 42789: Add tests for process_ordrsp() cancellation handling
Tests verify that EDI ORDRSP cancellations now use
Koha::Acquisition::Order->cancel() rather than a raw ModOrder call:

- Order status, cancellation reason and date are set correctly
- On-order items linked via aqorders_items are deleted
- Purchase suggestions linked to the biblio are reverted from
  ORDERED back to ACCEPTED
- Non-cancellation actions update suppliers_report only
- Missing ordernumbers are handled gracefully without dying

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Sharon Dugdale <sharon.dugdale@cumberland.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:27 +00:00
Martin Renvoize 70f5d1d3f1
Bug 42789: Add missing POD for _validate_location_code and _validate_collection_code
These private subs were added without pod_coverage documentation,
causing a QA failure. Add minimal =head3 blocks to resolve it.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Sharon Dugdale <sharon.dugdale@cumberland.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:26 +00:00
Martin Renvoize 6c55a4e7d4
Bug 42789: EDI ORDRSP cancellation should use Koha::Acquisition::Order->cancel()
The process_ordrsp() function was calling C4::Acquisition::ModOrder()
directly when processing a supplier cancellation, which only updated
the order status fields and left associated items and purchase
suggestions untouched.

Replace with Koha::Acquisition::Order->cancel(), which mirrors the
behaviour of manual cancellation via the acquisitions UI:
- Deletes on-order items via safe_delete() with error capture
- Reverts any linked purchase suggestion from ORDERED back to ACCEPTED
- Updates orderstatus, cancellationreason and datecancellationprinted

The delete_biblio option is intentionally omitted (defaults to false)
as silent biblio deletion is not appropriate for an automated EDI flow.

ModOrder is retained for the non-cancellation branch which only needs
to update suppliers_report.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Sharon Dugdale <sharon.dugdale@cumberland.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:25 +00:00
Kyle M Hall 1031a1ba73
Bug 42700: Remove dead $flags->{WAITING} code from C4::Members::patronflags
After the previous patch, $flags->{WAITING} from
C4::Members::patronflags is no longer read anywhere in Koha.

Test Plan:
1) Apply this patch
2) prove t/db_dependent/SIP/ t/db_dependent/Members.t
3) Note all tests pass!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:24 +00:00
Kyle M Hall 69d9cc4e59
Bug 42700: Avoid duplicate holds query query when building SIP patron object
This patch fetches the patron's holds once into a list of
Koha::Hold objects and derives both hold_items and unavail_holds
from that single fetch.

Test Plan:
1) Apply this patch
2) prove t/db_dependent/SIP/
3) Note all tests pass!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:23 +00:00
Kyle M Hall eb57856091
Bug 42700: hold_items in SIP patron info should contain all holds, not just waiting
Some 3rd party SIP clients calculate the number of holds ready
for pickup as:

  holds ready for pickup = hold_items - unavail_holds

Right now this can produce a negative number since hold_items
contains only waiting holds.

This patch makes hold_items contain all of the patron's holds,
so hold_items - unavail_holds correctly gives the count of holds
ready to be picked up.

Test Plan:
1) Apply the first patch only
2) prove t/db_dependent/SIP/Patron.t
3) Note the new "hold_items counts all holds" subtest fails
4) Apply this patch
5) prove t/db_dependent/SIP/Patron.t
6) Note the tests pass!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:22 +00:00
Kyle M Hall d8cf6da3a2
Bug 42700: Add unit tests
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:21 +00:00
Pedro Amorim b33dae39ad
Bug 41649: DBIC
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-18 11:21:17 +00:00
Matt Blenkinsop 72b8e1d9ea
Bug 42018: Prevent null values from breaking patron duplicate matching
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:37 +00:00
Matt Blenkinsop dbd680caec
Bug 42018: Add unit test
Test plan:
1) Apply just the unit test patch
2) Run t/db_dependent/Koha/Patrons.t
3) Test should fail
4) Apply remaining patch
5) Repeat step 3
6) Test should pass

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:36 +00:00
Aleisha Amohia 37ef59a9c8
Bug 35729: Normalize ISBNs as ISBN-13 if cannot fallback to ISBN-10
ISBNs beginning with 979 do not have an ISBN-10 version, and need to be normalized to an ISBN-13 format. All others, if valid, can safely be normalized to ISBN-10 format (current behaviour).

To set up

1. Download the attached sample records with 979 ISBNs
2. Log into the staff interface and go to Cataloguing
3. Go to Stage MARC records for import and upload the sample MARC file
4. Stage and import the biblio records
5. Once imported, open the detail pages for some of the records
6. In another tab, do a catalogue search and open the detail pages for ISBN-10 records, for example biblionumbers 6 and 54

To test

1. Apply the patch and restart services
2. Log into the staff interface and go to Koha Administration, Global system preferences, Enhanced Content tab
3. Cycle through each cover image service, enabled by the system preferences
4. With each cover image service is enabled, refresh the record detail pages you have open. The ISBN-10 records should CONTINUE to show their cover images. The newly imported ISBN-13 records should NOW show their cover images.
5. Confirm tests pass: t/db_dependent/Koha.t

testing with ISBN 9798885795692 should return this image from Amazon: https://images-na.ssl-images-amazon.com/images/P/9798885795692.01.LZZZZZZZ.jpg
testing with ISBN 9798876536914 should return this image from Amazon: https://images-na.ssl-images-amazon.com/images/P/9798876536914.01.LZZZZZZZ.jpg

Sponsored-by: Horowhenua Libraries Trust
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:35 +00:00
Andrew Fuerste Henry d3d4a6ca96
Bug 40799: (QA Follow-up) tidy template file
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:34 +00:00
Hammat Wele be7436b429
Bug 40799: Loading spinner not removed because .cover-slider is now used instead of .cover-slides
After Bug 32642, the spinner on cover images is hidden by verify_cover_images() which removes the .cover-slides class from the cover container.

However, Bug 33848 modified the SCSS so that the spinner is now triggered by .cover-slider instead of .cover-slides.

As a result, the spinner is never removed, because verify_cover_images() only removes .cover-slides.

Test plan

1- Follow the test plan from Bug 32642.
   ==> The spinner is displayed and never disappears.
2- Apply the patch
3- Reload the page.
   ==> The spinner disappears once the cover image is loaded.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:33 +00:00
Owen Leonard 813fb3d143
Bug 43260: (follow-up) Move debug menu JS into js_includes.inc
The _() function isn't available to JS in intranet-bottom.inc.

To test, apply the patch and test that the correct strings are
translatable. In this example I'm testing fr-FR:

- Update a translation:

  > gulp po:update
  > cd misc/translator
  > perl translate update fr-FR

- Open the corresponding .po file for the strings pulled from
  the template e.g.  misc/translator/po/fr-FR-staff-prog.po
- Locate strings pulled from includes/js_includes.inc for
  translation, e.g.:

  #: koha-tmpl/intranet-tmpl/prog/en/includes/js_includes.inc:105
  #, fuzzy
  msgid "Disable custom CSS"
  msgstr "Désactiver "

- Remove the "fuzzy" line.
- Edit the "msgstr" string however you want (it's just for testing)
- Install the updated translation:

  > perl translate install fr-FR

Log in to the staff interface as a user with debug permission. Test the
debug menu in the header, confirming that the strings look correct and
that the toggles work correctly.

Switch to your translated templates and test again.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:32 +00:00
Owen Leonard b567a5589f
Bug 43260: Debug options in navigation bar untranslatable
These patches update the templates which control the display of the
debug menu item in the staff interface header. The strings need to be
wrapped in the _() i18n function.

The patch also corrects capitalization and uses the term "custom"
instead of "user."

A follow-up patch will move this JS into js_includes.inc so that the _()
is available. This patch is separate to make it easier to view the diff.

Testing requires the follow-up.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:31 +00:00
Baptiste Wojtkowski 3d41c25c08
Bug 43245: Redirect user if no email was provided
TO TEST:
0 - Apply patch
1 - Make sure OPACResetPassword is activated
2 - Go to that good old Henry Acevedo's page and reset their password
3 - Notice you have been redirected with an error message telling you
have no email

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:30 +00:00
Pedro Amorim 1a5be8e287
Bug 42112: DBRev 26.06.00.014
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:29 +00:00
Lucas Gass 74130f08df
Bug 42112: Add option when changing a patron's category: allow choosing what to do for the expiry date and messaging preferences
To test:
1. APPLY PATCH, updatedatabase, restart_all
2. Find 2 new system preferences UpdateDateExpiryOnCategoryChange and UpdateMessagingPrefsOnCategoryChange
3. Each was the same options:
    -Never update
    -Always ask, default to no
    -Always ask, default to yes
    -Always update without asking

4. Go to  Administration > Patron categories
5. Edit or create some categories with different Enrollment periods and messaaging preferences
6. Try each of the options for BOTH system preferences, making sure they work as described.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:28 +00:00
Jonathan Druart e1072702f8
Bug 41896: Remove C4::Context from Koha::Cache
Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>

Amended-by: Jonathan Druart
Restore ||= op
-    $self->{namespace} = Koha::Config->get_instance->get('memcached_namespace') || 'koha';
+    $self->{namespace} ||= Koha::Config->get_instance->get('memcached_namespace') || 'koha';

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:27 +00:00
Andrew Fuerste Henry d154cda830
Bug 35797: (QA follow-up) Use https in API comment
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:26 +00:00
Shi Yao Wang b590f1a0a3
Bug 35797: Syspref, schema path file and tests changes
1- Corrected tests according to the comments
2- Removed unused import
3- Added syspref check of 'patronimages'
4- Tweaked swagger path schema

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:25 +00:00
Shi Yao Wang ad190dcc5d
Bug 35797: REST API Add GET route for patronimage and tests
Api changes adding GET route for patronimage

Test plan:
1- Apply the patch
2- Run `npm run api:bundle`
2- Go to administration -> sys.pref. -> set patronimages to allow
3- Log in koha using a user with "list_borrowers" or "edit_borrowers" privileges
4- Have a patron with a profile image (add an image if necessary by
searching for a patron and then clicking on the default profile image on
the top left)
5- Open a an API testing tool (such as postman), write
{intranet_url}/api/v1/patrons/{borrowernumber}/default_image
where {intranet_url} is the base intranet url of koha and {borrowernumber}
is the borrowernumber of a borrower that has an image
6- Add OAuth 2.0 credentials coming from koha (patron -> more -> manage
api keys) and generate a token
7- Send the request and notice the image of the patron is returned
8- Do step 5-7 with a borrowernumber of a patron without an image and a
borrowernumber of a patron that doesn't exist (and other edge cases
where there should be an error message displayed such as "Access
forbidden", "Authentication required", etc.)
9- Notice an appropriate error message is displayed

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>

Bug 35797: Rename test file from 'image.t' to 'patron_images.t'

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:24 +00:00
Martin Renvoize 3450f46d8b
Bug 40736: (QA follow-up) Avoid redundant CGISESSID cookie lookups
The cookie is already fetched once into $current_session_cookie
earlier in login(); reuse it instead of calling
$c->req->cookie('CGISESSID') again at each check site.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:23 +00:00
Lari Taskula b094346030
Bug 40736: Add test
To test:
1. prove t/db_dependent/api/v1/idp.t

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:22 +00:00
Lari Taskula 1f86fb5616
Bug 40736: Restore commented out tests
Commented out by https://bugs.koha-community.org/bugzilla3/attachment.cgi?id=143438

(or 9c5f6e8c17 (diff-9e449131ec9eb973ff6a851c0fda9a426255591e3f579c79b047fb2763f33e80R275) )

To test:
1. prove t/db_dependent/api/v1/idp.t

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:21 +00:00
David Cook 20bde64702
Bug 40736: Gracefully handle missing CGISESSID cookie
This change makes the REST API gracefully handle
a missing CGISESSID when trying to do an OAuth/OIDC login
by direct linking.

Test plan:
0. Apply the patch and koha-plack --restart kohadev
1. Set up an OpenID Connect client using the wiki
https://wiki.koha-community.org/wiki/Testing_SSO
2. In an incognito/private window, try directly logging in
with the following URL:
http://localhost:8080/api/v1/public/oauth/login/test/opac
3. Note that you're redirected to opac-user.pl with an auth error
message of "No user session found"
4. Close the incognito/private window
5. In an incognito/private window, try directly logging in
with the following URL:
http://localhost:8081/api/v1/oauth/login/test/staff
6. Note that you're redirected to mainpage.pl with an auth error
message of "No user session found"

Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:20 +00:00
Hammat Wele 6490728051
Bug 33758: Add a parameter to use the intranet_catalog_biblio_enhancements_toolbar_button Plugin hook
This patch adds a parameter to call the method intranet_catalog_biblio_enhancements_toolbar_button with a biblionumber in the page catalogue/detail.pl.

Modification of cat-toolbar.inc to display new buttons correctly.

To test :
1. Apply the patch
2. Take a plugin who implements the method intranet_catalog_biblio_enhancements_toolbar_button
3. Check if the parameter biblionumber is passed correctly at this method
4. Create the new button in HTML in the plugin method
(button must be like the ones that are in the toolbar of the page detail.pl)
5. Go to the page in browser and check if the new button is displayed

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:19 +00:00
Martin Renvoize 1e0442b847
Bug 41105: (QA follow-up) DRY out gender label rendering
The gender-code-to-label mapping (F/M/O -> Female/Male/Other) was
duplicated between the results table cell and the edit-form select
options in modborrowers.tt. Extract it into a shared
patron-sex-label.inc so there is a single place defining the mapping,
and drop the now-unused "lib" values from the sex options in
tools/modborrowers.pl (labels are supplied by the include, keyed on
the option value, rather than by Perl).

Test plan:
1. Navigate to Patrons and click Search, to display a list of patrons.
2. Select all results and click "Batch patron modification".
3. Verify the "Gender" column displays Female/Male/Other correctly.
4. Verify the "Gender" edit dropdown still offers Female/Male/Other
   and saves correctly.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:18 +00:00
Martin Renvoize b51a3f111a
Bug 41105: (QA follow-up) Fix gender label translation
The batch patron modification "Gender" column and edit dropdown
hardcoded "Female"/"Male"/"Other" as data passed from
tools/modborrowers.pl, and the "Other" gender label was displayed as
plain text.

Values coming from Perl as template data are never picked up by the
PO extractor, so the gender select options in the edit form could
never be translated. Elsewhere in Koha (moremember.tt,
moremember-brief.tt, memberentrygen.tt, opac-memberentry.tt) the
"Other" gender label is wrapped with tp('gender', 'Other') to give
translators context, since "Other" is ambiguous out of context.

This makes the "Gender" dropdown options translatable directly from
the template, and aligns "Other" with the existing tp('gender', ...)
convention used everywhere else gender is displayed.

Test plan:
1. Navigate to Patrons and click Search, to display a list of patrons.
2. Select all results and click "Batch patron modification".
3. Verify the "Gender" column and "Gender" edit dropdown still work as
   expected.
4. Confirm (e.g. via the .pot file or misc/translator/translate) that
   "Female", "Male" and the "Other" (gender) msgctxt string are now
   extracted for translation from this template.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:17 +00:00
Olivia Reynolds 0bb26abd5a
Bug 41105: Add gender to batch patron modification
This patch adds the ability to batch-edit the gender field for patrons.

Test plan:
1.  Ensure there are a number of patrons in the database.
2.  Apply patch.
3.  Navigate to Patrons and click Search, to display a list of patrons.
4.  Select all results and click "Batch patron modification".
5.  Verify the "Gender" column is displayed.
6.  Verify the "Gender" column may be toggled under the "Columns"
    dropdown.
7.  Select a subset of the patrons for modification.
8.  Verify there is a "Gender" option beneath the table.
9.  Select "Female" as the gender, and click "Save".
10. Verify the gender of all selected patrons was set to "Female".
11. Repeat with "Male" and "Other" as the gender.
12. Repeat with clearing the gender field by ticking the checkbox next
    to the dropdown and saving. Verify the gender field is blank for
    each selected patron.

Sponsored-by: Open Fifth
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Amit Gupta <amit.gupta@informaticsglobal.ai>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-14 12:09:15 +00:00
Pedro Amorim 662020e21b
Bug 42714: RM follow-up: Fix tests
prove t/00-load.t
prove xt/author/pod_coverage.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-13 11:28:40 +00:00
Owen Leonard 55bc6f01e0
Bug 41842: (follow-up) Ensure that tidy doesn't trim wanted whitespace
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 16:08:01 +00:00
Owen Leonard 1b00485b27
Bug 41842: (follow-up) Make sure this space doesn't get tidied
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 16:07:59 +00:00
Martin Renvoize 3cd648d52f
Bug 41842: Use space-dash-space separator between course name and section
The staff interface templates were using a bare hyphen with no spaces
when displaying course name and section together, while the OPAC templates
(Bug 42654) use ' - ' (space-dash-space). This brings the staff interface
into alignment with the OPAC convention for consistency and readability.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 16:07:58 +00:00
Owen Leonard 303ba57409
Bug 41842: Improve translation of course reserves headings
This patch adds some use of I18N functions to course reserves templates
in order to improve the process of translation by adding context and
placeholders. The patch also rewords some headings for clarity and
consistency.

To test, apply the patch and go to course reserves.

- Add a course if necessary.
- Click the course to view it.
- On the course details page, confirm that the page title, breadcrumbs,
  and heading look correct.
- Click "Edit" in the toolbar.
- Check the page title, breadcrumbs, and heading again.
- Return to the course details and click "Add reserves" and check that
  page.
- Submit a barcode or biblionumber and check that page.
- Test the "Batch add reserves" page.

To test the improved I18N, apply the patch and test that the correct
strings are translatable. In this example I'm testing fr-FR:

- Update a translation:

    > gulp po:update
    > cd misc/translator
    > perl translate update fr-FR

- Open the corresponding .po file for the modified strings, e.g.
  fr-FR-messages.po.
- Locate strings pulled from course reserves templates for
  translation, e.g.:

    #: koha-tmpl/intranet-tmpl/prog/en/modules/course_reserves/course.tt:10
    #: koha-tmpl/intranet-tmpl/prog/en/modules/course_reserves/course.tt:42
    #: koha-tmpl/intranet-tmpl/prog/en/modules/course_reserves/course.tt:56
    #, fuzzy, perl-brace-format
    msgid "Edit course {course_name}"
    msgstr "Modifier {course_name}"

- Remove the "fuzzy,"
- Edit the "msgstr" string however you want (it's just for testing)
- Install the updated translation:

  > perl translate install fr-FR

Switch to the translation you updated. Walk through the process of
editing a course and adding course reserves to confirm that your translated strings show up.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 16:07:55 +00:00
Pedro Amorim 4960d6987d
Bug 42973: (RM follow-up) Update tests number
Because of bug 42866
prove t/db_dependent/Koha/AdditionalContents.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:49:52 +00:00
Laura_Escamilla 89f6fcc4cd
Bug 42973: Unit test
Added a unit test to confirm that search_for_display orders manually positioned news first, followed by unnumbered news by publication date descending.

Test passes:

prove t/db_dependent/Koha/AdditionalContents.t

Signed-off-by: Gretchen Maxeiner <MaxeinerGL@gcc.edu>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:49:51 +00:00
Laura_Escamilla 70caff963a
Bug 42973: Restore manual ordering for news
To test:
1. Create four OPAC news items:

   - "Pinned news item" with "Appear in position" = 1 and an older publication date.

   - "Newest unnumbered news item" with no position and the newest publication date.

   - "Middle unnumbered news item" with no position and an intermediate publication date.

   - "Oldest unnumbered news item" with no position and the oldest publication date.

2. View the OPAC main page.

3. Confirm the news items are ordered by publication date, with the newest item first.

Apply the patch.

4. Reload the OPAC main page.

5. Confirm the manually positioned news item appears first.

6. Confirm the remaining news items are ordered by publication date descending.

7. Confirm news items without an "Appear in position" value continue to display newest first.

8. Sign off and have a wonderful day!

Signed-off-by: Gretchen Maxeiner <MaxeinerGL@gcc.edu>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:49:51 +00:00
Katrin Fischer bb53e653f3
Bug 43239: (QA follow-up) Rephrase hint on subscription form
This is to clarify that the item's current item type will
not be changed, no matter what settings have been used in
the subscription.

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:49:50 +00:00
David Nind 5005019b5d
Bug 43239: (follow-up) Update wording for hint
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:49:50 +00:00
Emmi Takkinen 02515ebcc7
Bug 43239: Preserve item item type if previousitemtype column is undefined
It is possible to create serial item with wrong itemtype if syspreference
makePreviousSerialAvailable is enabled and field "item type for older issues"
isn't filled when creating a subscription for biblio record. This happens
when multiple items are created. In installations where itemtypes differ
between items and biblios, this causes data inconsistencies and leads to
errors in biblios Holdings table.

This patch changes behaviour of serials-edit.pl so that if
makePreviousSerialAvailable is enabled and subscriptions previousitemtype
column is NULL, previous serial items itype column is preserved. It also
adds a hint text to subscription form about this change.

To test:
1. Enable systempreference makePreviousSerialAvailable.
2. Create new biblio record.
3. Create a new subscription for biblio record you made, select option
"Create an item record", select different item type from "Item type"
selection than biblio record has but leave selection "item type for older
issues" empty.
4. Receive a serial item for subscription. Check from biblio records
Holdings table that its item type matches item type selected in step 3.
5. Now receive 3 serial items. After creating them check Holdings table again.
=> Note that first item created now has same item type as biblio record.
6. Apply this patch, restart services.
7. Create new subscription, fill needed fields as in step 3.
=> Note that there is a new hint text after field "item type for older issues".
8. Repeat steps 4. and 5.
=> Note that now all items have same item type.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:49:49 +00:00
Pedro Amorim a0ac15d8b3
Bug 35104: DBRev 26.06.00.013
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:49:49 +00:00
Martin Renvoize 1b4373eea3
Bug 35104: Update Exporter/Record.t fixture for the new store() validation
This test built a "bad" biblio by storing invalid MARCXML directly via
Koha::Biblio::Metadata->store(), which now rejects it. Simulate
legacy corrupt data already in the database by writing it straight to
the DBIC row, bypassing the new validation, instead.

Signed-off-by: Baptiste Bayche <baptiste.bayche@inlibro.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Sponsored-by: OpenFifth
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:45:06 +00:00
Martin Renvoize 6726b1a254
Bug 35104: Catch invalid metadata and surface repair warnings in AddBiblio/ModBiblio
AddBiblio/ModBiblio/ModBiblioMarc gain an optional warnings arrayref
option: any repair messages recorded by Koha::Biblio::Metadata->store()
(see previous commit) are appended to it via object_messages, so a
caller can show the cataloguer what was automatically fixed instead of
the fix happening invisibly.

AddBiblio also now distinguishes Koha::Exceptions::Metadata::Invalid
from other errors in its catch block and rethrows it, so a caller can
tell "this record is unrecoverable" apart from an unexpected internal
error and react accordingly (see following bugs for callers doing so).

The inline StripNonXmlChars calls in TransformHtmlToMarc are removed,
since stripping is now handled centrally and consistently by
Koha::Biblio::Metadata->store() rather than ad-hoc at every call site.

Signed-off-by: Baptiste Bayche <baptiste.bayche@inlibro.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Sponsored-by: OpenFifth
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:45:05 +00:00
Martin Renvoize 112712e31c
Bug 35104: Recover MARCXML that fails to decode, or throw when unrecoverable
Koha::Biblio::Metadata->store() now validates that MARCXML round-trips
through MARC::Record before saving it, rather than saving whatever was
given and letting corruption surface later as an opaque failure
somewhere downstream.

Two faults are recovered automatically, and either or both may apply
to the same record:

- non-XML characters (recorded as nonxml_stripped, recovered via
  StripNonXmlChars)
- datafields with no subfields at all, which MARC::File::XML refuses
  to parse (recorded as empty_datafield_stripped, recovered by
  removing the empty datafield)

Each recovered fault is logged, reported via object_messages for the
caller to act on, and recorded as a biblio_metadata_errors row (tag
and subfield included where identifiable) for later review - except
where an identical row already exists for the same metadata_id, so a
source record that stays dirty across repeated saves (e.g. re-harvested
from an external repository) doesn't pile up duplicate rows for the
same fault. Existing rows are otherwise left alone on a clean save,
since they're review flags requiring explicit resolution, not a cache
to invalidate.

If the MARCXML cannot be recovered at all, Koha::Exceptions::Metadata::Invalid
is thrown. The recovery logic is exposed as a repair_marcxml class
method so other callers that parse MARCXML ahead of a store() (see
following bugs) can reuse it instead of re-implementing it.

Signed-off-by: Baptiste Bayche <baptiste.bayche@inlibro.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Sponsored-by: OpenFifth
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:45:04 +00:00
Martin Renvoize b80a26eb51
Bug 35104: Add Koha::Biblio::Metadata::Error(s) object classes
Thin Koha::Object(s) wrappers around biblio_metadata_errors. error_type
is restricted to a known set of constants, validated on store(), so a
typo'd or unknown value fails loudly rather than writing an
unrecognised value into the database. Errors::by_type() gives callers
a named filter instead of hand-rolling the same search().

Signed-off-by: Baptiste Bayche <baptiste.bayche@inlibro.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Sponsored-by: OpenFifth
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:45:02 +00:00
Martin Renvoize 50b0ba401b
Bug 35104: Guard Koha::Exceptions::Metadata::Invalid::full_message against undef fields
full_message() interpolated id/biblionumber/format/schema/decoding_error
directly into a sprintf without checking definedness, producing Perl
warnings whenever one of them is unset (e.g. a new, not-yet-stored
metadata object with no id yet).

Signed-off-by: Baptiste Bayche <baptiste.bayche@inlibro.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Sponsored-by: OpenFifth
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:45:01 +00:00
Martin Renvoize 0490204f7b
Bug 35104: Add koha_object_class to BiblioMetadataError schema result
Signed-off-by: Baptiste Bayche <baptiste.bayche@inlibro.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Sponsored-by: OpenFifth
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:45:00 +00:00
Martin Renvoize 7d9562c1b6
Bug 35104: Automated Schema Update
Signed-off-by: Baptiste Bayche <baptiste.bayche@inlibro.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Sponsored-by: OpenFifth
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:44:59 +00:00
Martin Renvoize 22b4acf8d2
Bug 35104: Add biblio_metadata_errors table for per-fault error tracking
Adds biblio_metadata_errors to record individual faults (non-XML
characters, empty datafields, ...) found and recovered when saving a
bibliographic record's MARCXML, so they can be surfaced to
cataloguers and explicitly resolved once reviewed.

Signed-off-by: Baptiste Bayche <baptiste.bayche@inlibro.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Sponsored-by: OpenFifth
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-12 15:44:58 +00:00
Laura_Escamilla 1ffbc32ad2
Bug 43019: Prevent access to pages belonging to another library (staff side)
To test:
1. Create two patron accounts:
    * One patron whose home library matches the library assigned to the page.
    * One patron whose home library is different from the library assigned to the page.
2. Create a new page with the following settings:
    * Display location = OPAC
    * Library = a specific library
    * Publication date = Today
    * Title and Content = minimally necessary content
3. Save the page.
4. Right-click the OPAC link and copy the link address.
5. Open the link in an incognito or private browser window without signing in.
    * Confirm that the library-specific page is not displayed.
    * Confirm that a 404 page is shown.
6. In the incognito window, sign in using the patron account whose home library matches the page library.
7. Open the copied OPAC link again.
    * Confirm that the page is displayed.
8. Sign out and sign in using the patron account whose home library does not match the page library.
9. Open the copied OPAC link again.
    * Confirm that the page is not displayed.
    * Confirm that a 404 page is shown.
10. Edit the page and change Library to All libraries.
11. Open the OPAC link in an incognito or private browser window without signing in.
    * Confirm that the page is displayed.
12. While signed in as each patron account, open the OPAC link again.
    * Confirm that the page is displayed for patrons from both libraries.
13. Create or edit a page with the following settings:
    * Display location = Staff interface
    * Library = a specific library
    * Publication date = Today
    * Title and Content = minimally necessary content
14. Sign in to the staff interface using a staff account whose library matches the page library.
15. Open the page from the staff interface.
    * Confirm that the page is displayed.
16. Sign out and sign in using a staff account whose library does not match the page library.
17. Open the staff page link directly.
    * Confirm that the page is not displayed.
    * Confirm that a 404 page is shown.
18. Edit the staff page and change Library to All libraries.
19. Open the page while signed in with staff accounts from both libraries.
    * Confirm that the page is displayed for both accounts.
20. Repeat the OPAC and staff interface checks using Display location = Staff interface and OPAC.
    * Confirm that the page is displayed in both interfaces for users from the matching library.
    * Confirm that users from a different library receive a 404 page.
    * Confirm that pages assigned to All libraries are available in both interfaces.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:48 +00:00
David Cook e50267072f
Bug 43019: Fix branch comparison
This change fixes the branch comparison between the Page
and the user. Previously, it was doing a numeric "!=" and now it
does the string comparison "ne". Also, if no environmental variable
is set for the default branch, it defaults to an empty string
for anonymous (ie non-authenticated) users.

Test plan:
0. Apply the patch
1. restart_all
2. Log into the staff interface
3. Go to Tools
4. Click on "Pages"
5. Create a Page for All Libraries for the OPAC
6. Copy the "OPAC" link in the "Page URL" table cell and open it
in a private window or a separate browser
7. Note that the page appears
8. Now change the "Page" to a specific library
9. Revisit the URL in the private window
10. Note that it now gives a 404 error
11. Log into the OPAC with a user whose library matches the libary
of the Page
12. Note that now you can view the URL again
13. Change the library of the user or the Page
14. Note that you cannot see the Page now

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:47 +00:00
Jonathan Druart a45638d8fc
Bug 42866: Prevent SQL injection in Koha::AddtionalContents->search_for_display
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:46 +00:00
Martin Renvoize f3f6f15938
Bug 42847: (QA follow-up) Clear session state on all login_core exit paths
login_core only cleared leftover session state on the failed-auth
branch. The earlier 'SIP access not authorized' return (unknown
terminal id) left any account/ils/institution/policy/credentials from
a prior connection on a reused prefork worker in place.

Move the reset to the top of login_core so every non-success exit path
leaves clean state, and only a successful api_auth re-establishes the
session. This keeps the no-authenticated-state-until-auth invariant in
a single place rather than duplicating the key list per branch.

Also extend the unit test to cover the unknown-terminal path on a
reused worker.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:45 +00:00
Kyle M Hall 50a4389813
Bug 42847: Prevent previously authenticated user
After a SIP terminal logs in successfully, a later connection handled by the
same worker could bypass authentication entirely. login_core() stores the account
in the session before calling api_auth(), and left it in place when authentication
failed. The check in raw_transport() that is meant to drop unauthenticated connections
only looks at $self->{account}->{id}, which is now populated straight from the SIP config,
so the connection is treated as logged in.

Test Plan:
1) Create a SIP user with the username and password "term1"
2) Restart the SIP server
3) Apply the first patch
4) prove t/db_dependent/SIP/MsgType.t
5) Note the new test fails
6) Telnet to the SIP server port ( 6001 for ktd )
5) Send a successful login: 9300CNterm1|COterm1
6) Note you get a 941 back
7) Disconnect, then telnet to localhost 6001 again
8) Send a bad login: 9300CNterm1|COterm2
9) Note you get a 940 back, but the connection stays open
10) Send a patron information request, using a real patron cardnumber:
    6300120260615    110310          AOCPL|AA23529001000463
11) Note you get a 64 patron information response with the patron's data back,
    even though the login failed!
12) Apply the second patch
13) prove t/db_dependent/SIP/MsgType.t
14) Note the subtest now passes!
15) Restart all the things!
16) Repeat steps 4 to 10
17) Note the the patron info request returns no patron data and the connection is dropped!

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:43 +00:00
Kyle M Hall d56dc2bfa8
Bug 42847: Add unit tests
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:42 +00:00
Jonathan Druart 887b163652
Bug 42749: Prevent SQL injection from GetInvoices (order_by)
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:41 +00:00
Martin Renvoize 5aa86845f5
Bug 42747: (QA follow-up) Parameterize query, dedupe, and log rejections
The original fix only whitelisted image_name at the point it's saved
in edit-layout.pl, leaving the actual injection sink in create-pdf.pl
unparameterized. This adds defense-in-depth by fixing the query itself,
avoids a redundant duplicate call to get_all_image_names() (already
fetched earlier in the script as $image_names), and logs rejected
image_name values instead of silently discarding them so tampering
attempts leave an audit trail.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:40 +00:00
Jonathan Druart bbbaa83c1c
Bug 42747: Prevent SQL injection via image_name in edit-layout
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:39 +00:00
Martin Renvoize b0c86b0fcf
Bug 42746: (QA follow-up) Sanitize agefield and substitution field at the sink too
The original fix only whitelisted agefield, condition_field, and
substitution_field at the point they're saved in
tools/automatic_item_modification_by_age.pl, leaving the actual
consumer of the stored syspref, C4::Items::ToggleNewStatus, trusting
agefield and substitution field unchecked. agefield is spliced
directly into raw SQL (TO_DAYS($agefield)) and so can't be
parameterized like an ordinary value; substitution field reaches a
dynamic method call ($item->$field($value)). Any future or
alternate writer of the automatic_item_modification_by_age_configuration
syspref would have reopened both holes without touching the form at
all.

This adds the same allow-list validation directly in ToggleNewStatus
(falling back to the safe default agefield, or skipping the
substitution/condition, exactly as before for missing values), shares
the age-field allow-list between the two files via a new
GetAutomaticItemModificationAgeFields() in C4::Items so it can't drift
between them, and logs rejected values via Koha::Logger instead of
silently discarding them so tampering leaves an audit trail.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:38 +00:00
Jonathan Druart 52f4b17b0f
Bug 42746: Sanitize condition and substitution fields
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Amended-by: Jonathan Druart:
Replace , with ;

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:37 +00:00
Jonathan Druart 2b09e846bc
Bug 42746: Sanitize age_field
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:36 +00:00
Martin Renvoize 8145e26a93
Bug 42471: (follow-up) prevent XSS in suggestion status
The custom/authorized-value status branch concatenated row.status
directly into a class="..." attribute string. escape_str() only
escapes &, <, > (not quotes), so a status code containing a double
quote could still break out of the attribute.

Build the status <span> via DOM APIs instead, the same way the
reason field was fixed: fixed statuses get a hardcoded class, and
the free-form status code goes into a data-status attribute via
dataset, which the DOM escapes safely regardless of content.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:35 +00:00
David Cook 222dac6c3c
Bug 42471: (follow-up) prevent XSS in suggestion reason
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:34 +00:00
Jonathan Druart acf1184436
Bug 42471: Prevent XSS in suggestion.tt
escape_str all the things

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:33 +00:00
Martin Renvoize da595ca8d5
Bug 30233: (follow-up) Fix POD formatting codes in _apply_substitution
The angle-bracket delimiter notation in the POD prose (e.g.
PATTERN<DELIM>) caused the POD parser to treat the trailing letter of
each word as an unknown formatting code (N<>, T<>). Replace with
C<s/PATTERN/REPLACEMENT/FLAGS> and a parenthetical note that any
non-space delimiter is accepted.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:32 +00:00
Martin Renvoize 8a06056143
Bug 30233: (follow-up) Add tests for _apply_substitution and combined modifiers
- t/C4/ClassSplitRoutine/RegEx.t (new): exercises _apply_substitution via
  the public split_callnumber interface — basic substitution, alternate
  delimiters, \n/$n backrefs, /gi combined flag, /e rejection, and malformed
  or uncompilable rules (including the new logger warn path).
- t/Koha/Regex/Replacement.t: extend the subst() helper to handle the ig/gi
  combined modifier and add a subtest that confirms case-insensitive global
  replacement and backref case preservation.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:30 +00:00
Martin Renvoize 45852aa193
Bug 30233: (follow-up) Log a warning when _apply_substitution discards a bad pattern
When eval { qr/$pattern/ } fails (e.g. the admin-configured s/// contains
an invalid regex), _apply_substitution previously returned silently with no
indication of why the rule was skipped. Add a Koha::Logger warn so the
failure appears in the log and is diagnosable without a debugger.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:29 +00:00
David Cook b9e7973649
Bug 30233: (follow-up) fix some errors reported by QA tools
Note: While the methodology in Koha::Regex::Replacement may seem
bespoke, it's similar to how Python's re.sub(pattern, repl, string)
regex substitution works.

Unit tests:
prove -v t/Koha/Regex/Replacement.t \
    t/SimpleMARC.t \
    t/db_dependent/MarcModificationTemplates.t \
    t/Labels_split_Regex.t

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:28 +00:00
Sanjar Tulkinov Anvar o'g'li 1043dfeda7
Bug 30233: Stop running call number split regexes as code
C4::ClassSplitRoutine::RegEx::split_callnumber ran the admin-configured rule
through eval "\$cn_item =~ $regex", executing arbitrary Perl. Parse the
rule as s<DELIM>PATTERN<DELIM>REPLACEMENT<DELIM>FLAGS, reject the /e flag,
compile the pattern with qr// and expand the replacement as data via
Koha::Regex::Replacement::expand_template, so the rule is applied without
eval.

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:27 +00:00
Sanjar Tulkinov Anvar o'g'li d7c1e19b49
Bug 30233: Stop running MARC modification replacements as code
Koha::SimpleMARC::_modify_values and Koha::Items::apply_regex wrapped the
user replacement in a string and ran it through s///ee, so an interpolation
block such as \@{[ system(...) ]} executed arbitrary Perl. Expand the
replacement as data via Koha::Regex::Replacement::expand_template under a
single /e instead, which keeps capture-group replacements working but never
runs the input as code.

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:26 +00:00
Sanjar Tulkinov Anvar o'g'li cda91ee42c
Bug 30233: Add Koha::Regex::Replacement to expand replacements as data
Add a small single-pass helper, Koha::Regex::Replacement::expand_template,
that expands a user-supplied regex replacement string as data: numbered
backreferences ($1..$n, ${n}, the legacy \1..\9 form), named captures
($+{name}, ${name}) and the \n \r \t escapes. Every other character is
copied through literally, so \@{...}/${...} interpolation blocks end up as
text rather than being run. This is used to replace the s///ee double-eval.

The routine is fully qualified at the call sites rather than exported, and
case folding is left out to keep it small.

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-11 12:39:23 +00:00
Owen Leonard 3b5f16adc6
Bug 40498: Improve style of list transfer form
This patch makes some style changes to the list transfer form. Since it
needs the same CSS that is used on the circulation page, the CSS block
is moved from circulation.tt to staff-global.scss.

The patch also changes the behavior of the form so that the "New owner"
line doesn't appear until after a patron has been selected.

To test, apply the patch and rebuild the staff interface CSS.

- If necessary, create a public list.
- From the list of public lists, click the "Transfer" button.
- Verify that the form looks correct. The patron search field should
  match the search field shown when you navigate directly to
  circulation.pl
- Search for a patron who has permission to manage public lists.
  - After you select a patron, a new line should appear:
    "New owner: [name]"
- Click "Transfer" and confirm that the process works correctly.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:36 +00:00
Jennifer Sutton 4110bcf3bf
Bug 30759: Add hint about the data that is sent via the Google Books API to OPACSuggestionAutoFill
Add information about the data that is sent to Google if the
OPACSuggestionAutoFill system preference is enabled.

Test plan:
1. Go to Administration > System preferences.
2. Search for the OPACSuggestionAutoFill system preference.
3. Note that there is no note about the data that is sent to
   Google if the system prference is enabled.
4. Apply the patch.
5. Refresh the page.
6. There is now a note about the data that is sent to Google.
7. Check that it reads well and makes ense.

Sponsored-by: Catalyst IT
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:36 +00:00
Martin Renvoize 9e90ca26f7
Bug 40800: SIP checkin incorrectly triggering alerts on successful transfer completion
When an item is checked in via SIP2 at the correct target branch after a
transfer, the system was incorrectly triggering an alert flag. This occurred
because the TransferArrived message was not being filtered out for accounts
with checked_in_ok="1", causing the alert logic to treat successful transfer
completion as a failure condition.

The TransferArrived message indicates successful transfer completion and is
purely informational - it should not trigger SIP alerts. This fix ensures
that TransferArrived messages are filtered out before alert evaluation,
preventing false positive alerts on successful operations.

Test Plan:
1. Set up a SIP2 account with checked_in_ok="1" in SIPconfig.xml
2. Via staff client, check out an item to a patron
3. Return the item to a different branch to trigger a transfer
4. Use misc/sip_cli_emulator.pl to check in the item at the target branch
5. Verify SIP response shows alert flag 'N' (no alert) instead of 'Y'
6. Test wrong branch checkin still shows alert flag 'Y' with CV04 type

Before: Transfer completion at correct branch incorrectly shows alert='Y'
After: Transfer completion at correct branch correctly shows alert='N'

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:35 +00:00
Pedro Amorim c6e52fe82b
Bug 41649: DBRev 26.06.00.012
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:35 +00:00
Martin Renvoize 4475df36bf
Bug 41649: DBIC Schema boolean update
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Marcus Michel <marcus.michel@rbkc.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:34 +00:00
Martin Renvoize c6798323cd
Bug 41649: Fix SIP Message tests for magnetic media
The tests for desensitize and alert flags were failing because
items were created with random itemtypes that could have
sip_magnetic=1. This caused unexpected behavior since magnetic
media items don't get desensitized and trigger alert flags.

Fix by explicitly creating itemtypes with sip_magnetic=0 in:
- test_checkout_desensitize
- test_renew_desensitize
- test_checkin_v2

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Marcus Michel <marcus.michel@rbkc.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:34 +00:00
Martin Renvoize cd728e5bb2
Bug 41649: Unit tests for magnetic media
Add unit tests for SIP magnetic media support.

Test plan
1) Run t/db_dependent/SIP/Transaction.t in ktd and confirm it passes

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Marcus Michel <marcus.michel@rbkc.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:33 +00:00
Martin Renvoize df7e600587
Bug 41649: Add option to set magnetic flag for item itemtypes
This patch adds the ability to set an item type as magentic for SIP
response purposes.

Test plan:
1) Run included database updates
2) Navigate to admin > itemtypes
3) Edit/Add an itemtype and scroll to the 'Sip media types' input
4) A new 'Sip magnetic' checkbox should be available
5) Confirm setting/unsetting it works as expected
6) The SIP magnetic flag bit should respect the setting
7) To test SIP functionality:
   a) Start the SIP server (e.g., koha-sip service)
   b) Create an item with an itemtype that has sip_magnetic checked
   c) Use misc/sip_cli_emulator.pl to send a checkout request:
      perl misc/sip_cli_emulator.pl -a localhost -p 6001 -su <sip_username> -sp <sip_password> -l <location_code> --patron <patron_cardnumber> --password <patron_password> --item <item_barcode> -m checkout
   d) Check the SIP response to ensure it handles magnetic media correctly (e.g., no desensitization flag)

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Marcus Michel <marcus.michel@rbkc.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:33 +00:00
Martin Renvoize 06bf94d455
Bug 41649: DBIC Build
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Marcus Michel <marcus.michel@rbkc.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:32 +00:00
Martin Renvoize 5968bf0468
Bug 41649: Add sip_magnetic field to itemtypes table
This patch adds the new sip_magnetic boolean flag field to the itemtypes
table to allow flagging the itemtype as magnetic for sip responses.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Marcus Michel <marcus.michel@rbkc.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:32 +00:00
Colin Campbell 303deda8d3
Bug 41649: Set magnetic_media in SIP responses
The `magnetic_media` flag was never set, it can be set from
`sip_media_type` if it's present. `sip_media_type` is set via the
Authorized Values system.

Checkins will not be sorted correctly by Biblioteca without this flag.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Marcus Michel <marcus.michel@rbkc.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:31 +00:00
Jonathan Druart 03dd4cf862
Bug 42529: Prevent the test to fail in the future
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:31 +00:00
Tomás Cohen Arazi e89478d560
Bug 42529: Remove GetAgeRestriction from C4::Circulation
GetAgeRestriction is no longer used by any caller. This patch removes
it from the exports list and deletes the old test file.

Test plan:
1. Run tests without this patch:
   $ ktd --shell
   k$ prove t/db_dependent/Circulation.t \
            t/db_dependent/Reserves.t \
            t/Circulation/AgeRestrictionMarkers.t
=> SUCCESS: Tests pass!
2. Apply this patch
3. Run:
   k$ prove t/db_dependent/Circulation.t \
            t/db_dependent/Reserves.t \
            t/db_dependent/Koha/Policy/Biblio/AgeRestriction.t
=> SUCCESS: Tests pass!
4. Sign off :-D

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Amended-by: Jonathan Druart
Removed this from the commit message:
"The function body is kept for now as it is not harmful and could be
used by third-party code. It is no longer exported."

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:30 +00:00
Tomás Cohen Arazi b9868ce2d2
Bug 42529: Use Koha::Policy::Biblio::AgeRestriction for issuing and holds
This patch updates CanBookBeIssued and CanItemBeReserved to use the
new policy class instead of calling GetAgeRestriction directly.

Test plan:
1. Run tests without this patch:
   $ ktd --shell
   k$ prove t/db_dependent/Circulation.t \
            t/db_dependent/Reserves.t
=> SUCCESS: Tests pass!
2. Apply this patch
3. Repeat step 1
=> SUCCESS: Tests pass!
4. Sign off :-D

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:29 +00:00
Tomás Cohen Arazi 8ebf3436cb
Bug 42529: Add Koha::Policy::Biblio::AgeRestriction
This patch adds age restriction handling to the Koha namespace:

- Koha::Biblio->age_restriction: metadata extractor that parses the
  agerestriction field using the AgeRestrictionMarker system preference.
  Returns the numeric age or undef. Uses single return with last pattern.
- Koha::Policy::Biblio::AgeRestriction->check($biblio, $patron): policy
  class that evaluates whether a patron meets the age restriction.
  Returns a Koha::Result::Boolean (with payload on restriction).

Test plan:
1. Apply this patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Koha/Policy/Biblio/AgeRestriction.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:07:29 +00:00
Olivia Reynolds 51e3d5435a
Bug 40668: Don't depend on checkboxes existing in drawCallback
The checkboxes this callback listens to events on are conditionally
rendered, depending on whether the user has permission to edit each
item. When moving from a page with no checkboxes displayed to one with
checkboxes displayed, the checkbox elements may not exist at the time of
this callback, so the listener is not attached and the checkboxes are
inoperable.

This patch fixes this by using a delegated event handler on the row,
which doesn't depend on the checkbox existing at the time.

Test plan:
1.  Enable the system preference EnableItemGroups.
2.  Set up a non-super-librarian patron, with permission to:
    - view the catalogue (catalogue)
    - create update and delete item groups (manage_item_groups)
3.  Place this patron's library in a group by itself, with "limit item
    editing" enabled. The patron should only be able to edit items from
    their own library.
4.  Pick an item in the catalogue, and add at least 40 items to it from
    a library the patron cannot edit items from. Add at least one item
    from the patron's library that they can edit. The 40 items are to
    ensure there is a complete (20 item) page that the patron cannot
    edit anything on.
5.  Create an (empty) item group on this catalogue entry.
6.  Log in as the unprivileged patron and navigate to the holdings view
    for the relevant catalogue entry.
7.  Navigate through the holdings table pages until there is a page with
    no checkboxes.
8.  Navigate to a table page with a checkbox. Select one of the
    checkboxes, and verify no actions appear at the top of table, even
    though you should have permission to add this item to a group.
9.  Apply this patch, and hard refresh to load the new JavaScript.
10. Repeat steps 6-8, verifying the checkbox now appears correctly.
11. Verify you can add the item to an item group using the now-appearing
    action.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:28 +00:00
Pedro Amorim 60725ab4f1
Bug 43204: Fix ILL availability Source column ignoring deep link
ill-availability.js renders results from ILL availability plugins.
Each result can carry a `url` (deep link to the record) and an
`opac_url` (generic fallback). The Title column correctly prefers
`url`, but the Source column links to `opac_url` unconditionally and
never checks `url`, so results always link to the bare OPAC URL even
when a deep link is available.

Make Source prefer `url`, falling back to `opac_url`, same as Title.

To test:
1) Install the latest release of the plugin:
   https://github.com/openfifth/koha-plugin-ill-avail-z3950/releases
2) Enable ILLModule, ILLCheckAvailability.
3) Set AdditionalFieldsInZ3950ResultSearch = 856$a, 999$c, 001.
4) Add Z39.50 server: Name KTD, Hostname localhost, Port 2100,
   Database biblios, Syntax MARC21/USMARC.
5) Configure the ILL z3950 plugin: tick the 'KTD' box, set bib ID
   field 999$c, deep link
   http://localhost:8081/cgi-bin/koha/opac-detail.pl?biblionumber=source_record_id,
   OPAC URL http://localhost:8080, tick the last 3 display
   checkboxes, leave Partner ID blank.
6) Add a new ILL request: type book, ISBN 0879307447, cardnumber 42,
   destination library Centerville.
7) Go to availability search, confirm the result links to the
   correct deep-linked biblio, not a bare fallback.

Signed-off-by: Edith Speller <Edith.Speller@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:27 +00:00
Jonathan Druart c39aa5f6a7
Bug 42451: Remove duplicate success message
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:26 +00:00
Jonathan Druart fb0fb890e2
Bug 42451: Remove JS error when converting a report
Test plan:
Create a report with: SELECT marcxml from biblioitems
Hit /reports/guided_reports.pl?op=list
Notice the "Update SQL" button in the "Update" column, Click "Update"
=> JS error
"Uncaught TypeError: can't access property "backdrop", this._config is undefined" (in Firefox)
And nothing happens
Apply this patch, reload, click the button
=> Success, the report is updated

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:25 +00:00
Laura_Escamilla e9b9509461
Bug 42235: Add message type header to OPAC messaging table
The OPAC messaging table includes an empty first column header. This column
contains the message type, such as Item due, Advance notice, Hold filled, and
Auto renewal. Because the header only contains a non-breaking space, it is
flagged by accessibility tools as an empty <th> element.

Add a translated "Message type" label to the first column header so it has
meaningful accessible text.

Test plan:
1. Log in to the OPAC
2. Go to "Your messaging settings"
3. Inspect the messaging table header
4. Confirm the first column header contains no meaningful text

Apply the patch:
5. Reload the page
6. Confirm the first column header contains "Message type"
7. Confirm the messaging table still displays correctly
8. Run an accessibility checker and confirm the empty table header error is resolved
9. Sign off and have a wonderful day! :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:24 +00:00
Laura_Escamilla 444cad89c7
Bug 42235: Add accessible label to DataTables control column in OPAC messaging
The OPAC messaging table includes a DataTables responsive control column
with no header text. This results in an empty <th> element and an empty
aria-label, which is flagged by accessibility tools.

Add a translated "Expand" label to the control column header so it has
meaningful accessible text.

Test plan:
1. Log in to the OPAC
2. Go to "Your messaging settings"
3. Inspect the messaging table header
4. Confirm the control column header contains no text

Apply the patch:
5. Reload the page
6. Confirm the control column header contains "Expand"
7. Confirm responsive row expansion still works
8. Run an accessibility checker and confirm the error is resolved
9. Sign off and have a wonderful day!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:23 +00:00
Pedro Amorim de230bc58b
Bug 42656: (QA follow-up) Use _abort_operation() for Local's 'not configured' errors too
_upload_file, _download_file, _list_files and _rename_file's 'no directory configured' checks still called add_message() directly, skipping the status persistence this bug added elsewhere in the file. Route them through _abort_operation() so all error paths in Local.pm stay consistent.

prove t/db_dependent/Koha/File/Transport/Local.t

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:22 +00:00
Martin Renvoize 4deb199163
Bug 42656: (QA follow-up) Make error status persistence consistent, with a full trace, across FTP, SFTP and Local
Two related inconsistencies remained across the three transport
backends' error-handling paths:

1) Only Koha::File::Transport::SFTP's _abort_operation() persisted an
   "errors" status snapshot to the status column on every failed
   operation. FTP never did, and Local had no _abort_operation() at
   all - so an SFTP transport's displayed status could reflect a
   single transient failure from real usage (e.g. an EDI cron job)
   while the exact same failure on FTP/Local left the stored status
   untouched (stale, possibly still showing the last explicit
   test_connection() result). This is exactly the kind of unclear
   status reporting bug 42656 is about.

2) FTP's _abort_operation() carried its extra detail under an
   operation_params key, while SFTP (and Local's plain add_message
   calls) used path. The change_directory() success payload had the
   same split: FTP used directory, SFTP and Local used path. The
   admin template's change_directory row display depended on FTP's
   mismatched key names, which meant it silently failed to show the
   tested directory for SFTP transports.

Adds a shared _record_error($operation, \%payload) to the
Koha::File::Transport base class - add_message() plus status
persistence - and routes all three backends' _abort_operation() (Local
gains one) through it. Renames FTP's operation_params/directory keys
to path throughout, matching SFTP and Local, and simplifies the
template to read operation.detail.path unconditionally instead of
ORing two differently-named keys.

_record_error() persists the full trace of operations recorded on the
transport object so far this session (every success leading up to the
failure), not just the failing operation in isolation - the same
"full trace" shape TestTransport::process() already builds for an
explicit test_connection() run. A long-lived transport used across a
multi-step operation (e.g. an EDI download loop touching several files
before one fails) will therefore show every successful step leading up
to the failure. It also logs the failure - including the built trace -
via Koha::Logger at warn level, so it's visible in the Koha logs
without needing to query the transport's status column.

Test plan:
1. prove -r t/db_dependent/Koha/File/ t/db_dependent/Koha/BackgroundJob/TestTransport.t t/db_dependent/Koha/Edifact/Transport.t t/db_dependent/api/v1/file_transports.t
   => All tests successful (48 tests total), including:
      - new "_abort_operation persists status consistently" subtests
        in FTP.t, SFTP.t and Local.t that reload the transport from
        the DB after a simulated failure and confirm the status
        column was updated with the correct operation code and path
        detail;
      - a new "_record_error() tests" subtest in Transport.t proving
        the persisted trace includes a prior success message plus the
        failure (not just the failure alone), and that a warn-level
        log line mentioning the operation and error was emitted.
2. koha-qa.pl -v 2 --more-tests
   => [PASS], no critic/pod warnings.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:21 +00:00
Martin Renvoize f8cf2aea42
Bug 42656: (QA follow-up) Fix dead status code in file_transport_test.inc
Two issues in the operation_description BLOCK's status text, both
flagged in review:

1) The IF/SET that computed the " passed."/" failed." status text sat
   between the SWITCH statement and its first CASE. Template Toolkit
   only executes CASE-labelled content inside a SWITCH block, so this
   was dead code - status was never actually set, and every operation
   row silently rendered with no passed/failed text at all. It also
   checked operation.error, a field that doesn't exist on the
   operation hash (the error message lives under operation.detail.error,
   and whether the operation failed is operation.status eq 'error');
   even if the code path were reachable it would always have taken the
   "passed" branch.

2) Relying on a leading space baked into the " passed."/" failed."
   string literals to separate them from the preceding </span> is
   fragile. Made the space explicit in the markup instead, and dropped
   the leading space from the string values.

Moves the status computation above the SWITCH (using operation.status),
and adds an explicit space before [% status | html %] on every CASE line.

Also adds t/db_dependent/Koha/BackgroundJob/TestTransport.t, which was
entirely missing: it exercises process() end-to-end (enqueue, then
process, then reload both the job and the transport) and confirms the
persisted trace records every operation in sequence - successes before
the failure that ended the run - rather than collapsing to just the
final error.

Test plan:
1. prove t/db_dependent/Koha/BackgroundJob/TestTransport.t
   => All tests successful, confirming process() persists the full
      operation sequence (order preserved, at least one success and
      one error present) on both the job and the transport's own
      status column.
2. Manually rendered the fixed operation_description BLOCK with
   Template Toolkit against mock operations (mixed success/error) and
   confirmed every row now shows "Connection passed.", "Change
   directory (/incoming) passed.", ..., "Change directory (/outgoing)
   failed.- error:No such directory" - status text present with
   correct spacing, where before the fix no status text rendered at
   all.
3. prove -r t/db_dependent/Koha/File/ t/db_dependent/Koha/Edifact/Transport.t t/db_dependent/api/v1/file_transports.t
   => All tests successful (47 tests total), no regressions.
4. koha-qa.pl -v 2 --more-tests
   => [PASS], no critic/pod warnings.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:20 +00:00
Nick Clemens 7d6dafef6d
Bug 42656: Improve file transport test display
This patch displays the directory tested in the background jobs results

It updates the language when test results are not stored for the transport and
links to the job when results are stored

To test:
1 - Setup an ftp file transport with good credentials
2 - Enter non-existent directories in incoming/outgoing
3 - Save
4 - Verify on save 'Never used' shows for status
5 - Refresh the page, status now:
     Connection ok
     Change directory ok
     Change directory failed
     Change directory ok
     Change directory failed
6 - Apply patch, restart all
7 - Edit/save the transport again, change nothing
8 - Confirm the page says 'No test results stored'
9 - Reload the page
10 - Confirm results are now:
     Connection ok
     Change directory ok
     Change directory failed
     Change directory ok
     Change directory failed
     Test job details
11 - Click on the job details link
12 - Confirm actual directory and errors are shown
13 - Edit the transport, use existing directories
14 - Save, reload
15 - Confirm the success results and link t background job

Signed-off-by: John Vinke <jvinke@austinseminary.edu>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:19 +00:00
Martin Renvoize a0433635fa
Bug 43092: Stop Local transport falling back to the Koha process's own cwd
Koha::File::Transport's three backends disagree on what
change_directory(undef) means: FTP navigates to the server root,
SFTP falls back to the SFTP session's own home directory, and Local
fell back to the configured download_directory or, if that's also
unset, silently to '.' - the Koha process's own working directory,
which has no relation to any configured transport location.

test_connection() calls change_directory(undef) unconditionally for
both the download and upload directory checks on every config save
(via the auto-enqueued TestTransport background job). For a Local
transport configured with only one of download_directory/
upload_directory, the untested direction's list_files() call would
silently list the Koha process's own cwd (e.g. the Koha install root)
instead of failing cleanly.

Add a private _working_directory($configured_field) helper that
returns the tracked change_directory() override or the given
configured field, but never falls back to '.'. _upload_file,
_download_file, _list_files and _rename_file now use it and record a
clear "not configured" error instead of silently acting on an
unrelated directory when neither is set.

FTP's root and SFTP's home-directory fallbacks are left as-is: unlike
Local's '.', they navigate to a real, meaningful location for that
specific connection rather than leaking unrelated filesystem content,
so they aren't part of this fix.

Test plan:
1. prove t/db_dependent/Koha/File/Transport/Local.t
   => All tests successful, including the new subtest proving that
      list_files()/rename_file()/download_file() fail cleanly (with a
      "not configured" error) rather than falling back to '.' when
      their respective directory isn't configured, while the
      configured direction (upload, in the test) still works.
2. prove -r t/db_dependent/Koha/File/ t/db_dependent/Koha/Edifact/Transport.t t/db_dependent/api/v1/file_transports.t
   => All tests successful (43 tests total), no regressions.
3. koha-qa.pl -v 2 --more-tests
   => [PASS], no critic/pod warnings.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:18 +00:00
Pedro Amorim 885d1f65dc
Bug 42766: (RM follow-up): Ensure ScriptLog exists in new installs
Fix:
prove t/db_dependent/check_sysprefs.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-06 16:05:15 +00:00
Casey Conlin c267c679a1
Bug 16631: Fix: Remove TT tags from kohaTable() initialization in guided_reports_start.tt
Sponsored-by: Ramapo Catskill Library System

Bug 16631: Update saved reports table to use use named DataTables columns.

RM amended: Fix commit title

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-04 10:20:59 +00:00
Martin Renvoize 0d0ba262fe
Bug 42915: (QA follow-up) Defer coded_values declaration until document ready
holds.js declared `coded_values` as a bare top-level global, evaluated
immediately at script-parse time, reading `all_libraries` which is only
ever defined by the including template (request.tt, or
patron-detail-tabs.inc for circulation.tt/moremember.tt). This works
today only because those templates happen to emit their `all_libraries`
script block before Asset.js("js/holds.js") in the rendered HTML - a
fragile, easy-to-break ordering dependency between separate template
files, not something holds.js itself can guarantee.

Move the assignment into the existing $(document).ready() handler,
setting it explicitly on `window` so it remains visible as a global to
datatables.js's build_query(), which reads `coded_values` unqualified.
This defers evaluation until all synchronous inline scripts (including
whichever template defined all_libraries) have already run, regardless
of future include/ordering changes.

To test:
1. Place holds on a title for two different pickup libraries
2. Confirm the holds table on reserve/request.tt, the patron's Holds
   tab (circulation.pl and moremember.pl) all load with no console
   errors
3. Confirm filtering the holds table by pickup library name/branchcode
   still works on all three pages

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-04 10:20:58 +00:00
Martin Renvoize c7d13ee7a3
Bug 40445: (follow-up) Move TT values out of kohaTable script blocks
xt/tt_kohaTable_tidy.t forbids mixing Template::Toolkit tags with
.kohaTable() calls inside the same <script> block, since such blocks
are expected to be pure JS. The cashup modal init calls in
pos/register.tt and pos/registers.tt embedded TT-derived values
(bankable amount, register float, reconciliation note settings,
cashup progress) directly alongside kohaTable() initialisation,
tripping the check.

This moves those TT expressions into the existing TT-tag-only
<script> block that precedes the kohaTable block in each template,
exposing them as a plain JS object (cashupModalData) that the
kohaTable block then reads.

Test plan:
1. Apply the patch.
2. prove xt/tt_kohaTable_tidy.t -- register.tt and registers.tt
   should no longer be listed among the failures.
3. Load the POS register and registers pages, confirm the trigger
   and confirm cashup modals still receive the correct bankable
   amount, register float, and reconciliation note settings.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-04 10:20:56 +00:00
Pedro Amorim fa6ef97625
Bug 42766: DBRev 26.06.00.011
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-04 10:20:55 +00:00
Nick Clemens 0905d6a89f
Bug 42766: (QA follow-up) Add 'SCRIPTS' to log viewer
Apply this patch and confirm you can limit to 'Scripts' and that 'Scripts' is not in
all caps in the results

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-04 10:20:54 +00:00
Nick Clemens 4b4eb136cb
Bug 42766: (QA follow-up) Broaden sensitive patterns
We are all lazy, we aren't going to type --password everytime, we should
protect 'p' and 'pass' as well

This could likely get broader too later

TO test:
1 - perl misc/devel/create_superlibrarian.pl --branchcode=CPL --categorycode=S --cardnumber 3 --userid=ted3 --p=dog
2 - Note password is logged
3 - Apply this patch
4 - perl misc/devel/create_superlibrarian.pl --branchcode=CPL --categorycode=S --cardnumber 33 --userid=ted33 --p=dog
5 - Password redacted in log
6 - perl misc/devel/create_superlibrarian.pl --branchcode=CPL --categorycode=S --cardnumber 333 --userid=ted333 --pass=dog
7 - Password still redacted

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-04 10:20:53 +00:00
Martin Renvoize 53d5112c3a
Bug 42766: Add tests for logscriptaction() and _scrub_argv()
t/db_dependent/Log.t
- Add subtest verifying logscriptaction() logs to the SCRIPTS module,
  respects the ScriptLog preference, prefixes loginfo with $0, appends
  the info parameter, and honours custom action values.

t/Koha/Script_scrub_argv.t
- New test file exercising all branches of _scrub_argv():
  --flag value form, --flag=value form, non-sensitive flags, case-
  insensitive matching, multiple sensitive patterns (token, secret,
  apikey, api_key), and a flag immediately followed by another flag.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-04 10:20:52 +00:00
Martin Renvoize 5db19647c9
Bug 42766: Add ScriptLog auto-logging and ARGV scrubbing to Koha::Script
Extends the INIT/END blocks added by Bug 42714 to also log non-cron
scripts (those using bare 'use Koha::Script') to the SCRIPTS action-log
module when the new ScriptLog system preference is enabled.

Also introduces _scrub_argv(), which redacts the value of any command-
line flag whose name contains a sensitive pattern (password, secret,
token, etc.) before the argument list is logged. Scrubbing applies to
both cron and non-cron scripts, covering both --flag value and
--flag=value forms. This prevents credentials passed on the command
line (e.g. misc/devel/create_superlibrarian.pl --password ...) from
appearing in the action log.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-04 10:20:51 +00:00
Martin Renvoize e6091dc13e
Bug 42766: Add logscriptaction() to C4::Log
Adds a logscriptaction() function parallel to cronlogaction() that logs
to the SCRIPTS action-log module rather than CRONJOBS, gated behind the
new ScriptLog system preference. Exported from C4::Log alongside the
existing cronlogaction().

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-04 10:20:49 +00:00
Martin Renvoize 57cceec6bc
Bug 42766: Add ScriptLog system preference
Adds a new YesNo system preference 'ScriptLog' in the Logging section
of system preferences, adjacent to CronjobLog. When enabled, the start
and end of non-cron Koha scripts (those using bare 'use Koha::Script'
rather than 'use Koha::Script -cron') will be recorded in the action
log under the SCRIPTS module.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-04 10:20:46 +00:00
Paul Derscheid fdebc68051
Bug 43091: Clean the OPAC Vue dist directory in js:clean
js:clean predates the OPAC islands build (bug 26355) and only removes
the intranet and Cypress dist directories, so stale chunks survive in
koha-tmpl/opac-tmpl/bootstrap/js/vue/dist/ across rebuilds.

- add the OPAC Vue dist directory to js:clean

Test plan:
1. yarn js:build, then apply this patch series and yarn js:clean
2. ls koha-tmpl/opac-tmpl/bootstrap/js/vue/dist/
   => empty, no stale staff-only chunks remain
3. yarn js:build
   => only islands.esm.js and a patron-self-renewal chunk are emitted

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-03 13:00:37 +00:00
Paul Derscheid 8ebc1d0f2b
Bug 43091: Stop emitting staff-only Vue islands into the OPAC dist
Both islands builds share modules/islands.ts, which hardcodes every
island. The OPAC compilation therefore emits staff-only lazy chunks
(acquisitions-menu, vendor-menu, admin-menu) into
koha-tmpl/opac-tmpl/bootstrap/js/vue/dist/, and the OPAC islands.esm.js
bundles the staff Pinia stores.

- split modules/islands.ts into shared machinery and per-application
  entry points (islands-intranet.ts, islands-opac.ts)
- declare each application's islands as a Map literal and feed it
  through registerIsland(), the same path plugin islands take
- pass store definitions into hydrate() instead of importing every
  store in the shared module
- keep the full plugin import surface (registerIsland, hydrate, Vue
  re-exports) on both entries via star re-exports
- point the rspack islands entries at the per-application modules

Test plan:
1. yarn build
2. ls koha-tmpl/opac-tmpl/bootstrap/js/vue/dist/
   => only islands.esm.js and a patron-self-renewal chunk are emitted
3. ls koha-tmpl/intranet-tmpl/prog/js/vue/dist/
   => acquisitions-menu, vendor-menu and admin-menu chunks are still
   present
4. In the staff interface, visit pages using the acquisitions, vendor
   and admin menu islands and verify they render
5. Enable self-renewal for a patron's category and put the patron
   inside the availability window, e.g.:
   UPDATE categories SET self_renewal_enabled = 1
     WHERE categorycode =
     (SELECT categorycode FROM borrowers WHERE userid = 'koha');
   UPDATE borrowers
     SET dateexpiry = DATE_ADD(CURDATE(), INTERVAL 7 DAY),
     debarred = NULL WHERE userid = 'koha';
6. Log into the OPAC as that patron and open 'your summary'
   => the patron-self-renewal chunk loads from
   /opac-tmpl/bootstrap/js/vue/dist/ and the self-renewal modal
   completes successfully

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-03 13:00:36 +00:00
Owen Leonard be1bc44511
Bug 43206: cn_browser plugin red background on exact match is hard to read
This patch updates the cn_browser plugin output so that table rows are
highlighted using row classes and in colors which have sufficient
contrast.

To test, apply the patch and set your 952$o tag to use the cn_browser
plugin:

- Administration -> MARC bibliographic frameworks -> BKS -> MARC
  Structure -> Tag 952 -> Edit subfields.
  - Under the "Full call number" tab (o), set the "Plugin" dropdown to
   "cn_browser.pl" and save your changes.
- Go to Cataloging and locate a bibliographic record using the
  cataloging search.
- Add or edit items. NOTE: In the testing data there is only one item
  with a callnumber and it is "FIC/kas"
  - In the "o - Full call number" field, type "FIC" and then click the
    plugin editor image.
    - You should see the record with the "FIC/kas" call number and a
      green row right above it (indicating where an item with
      the call number "FIC" would be in the results).
  - Revise your search using the form at the top, entering "FIC/kas".
    - The record row should now be highlighted in pale yellow.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Gretchen <maxeinergl@gcc.edu>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-03 13:00:35 +00:00
Martin Renvoize 4db4d8be55
Bug 43088: Reuse _current_directory() instead of duplicating pwd() calls
_change_directory(), _list_files() and _is_connected() each called
$self->{connection}->pwd directly for their log payloads/connectivity
check, duplicating exactly what _current_directory() now provides as a
proper, documented internal accessor. Route them through it instead -
no behaviour change, just removing the duplication now that the
wrapper exists.

Test plan:
1. prove -r t/db_dependent/Koha/File/
   => All tests successful (32 tests), including the existing
      current_directory() and list_files()/change_directory() coverage.
2. koha-qa.pl -v 2 --more-tests
   => [PASS], no critic/pod warnings.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-03 13:00:34 +00:00
Martin Renvoize 7a750c7e83
Bug 43088: Add a consistent current_directory() accessor to Koha::File::Transport
FTP, Local and SFTP list_files() were unified to return the same flat
shape, but consumers still had no portable way to ask a transport for
its current remote directory - the underlying libraries' own methods
of that name don't agree on what "cwd" means. Net::SFTP::Foreign's
cwd() is a pure read-only accessor, but Net::FTP's cwd() is a mutator:
called with no argument it defaults to "/", issues a real CWD command,
and returns a boolean rather than a path.

The koha-plugin-transport-browser plugin discovered this the hard way:
it read $transport->{connection}->cwd directly to capture the initial
working directory, which happened to work for SFTP but silently reset
the FTP connection to root and captured "1" (a boolean) as the "path"
on every request, breaking directory navigation for FTP transports.

Add a public current_directory() method to the Koha::File::Transport
base class, following the same connect-on-demand pattern as
list_files()/upload_file()/etc, with a _current_directory() hook each
subclass implements using its own real read-only accessor: pwd() for
FTP, cwd() for SFTP, and the tracked {current_directory} (falling back
to download_directory) for Local. Consumers should use this instead of
reaching into transport internals.

Test plan:
1. prove t/db_dependent/Koha/File/Transport.t
        t/db_dependent/Koha/File/Transport/FTP.t
        t/db_dependent/Koha/File/Transport/Local.t
        t/db_dependent/Koha/File/Transport/SFTP.t
   => All tests successful, including the new current_directory()
      tests subtest in each transport test file, which mock the
      underlying libraries (Net::FTP / Net::SFTP::Foreign) to assert
      each backend reads its own real read-only accessor.
2. koha-qa.pl -v 2 --more-tests
   => [PASS] for all four changed modules, no critic/pod warnings.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-03 13:00:33 +00:00
Martin Renvoize 5ca496f1b7
Bug 43078: Exclude '.' and '..' entries from SFTP file listings
The FTP and Local transports both explicitly filter out '.' and '..'
pseudo-entries when listing a directory, but the SFTP transport never
did - it relied on the assumption that Net::SFTP::Foreign's ls() already
excludes them. It doesn't: ls() returns exactly whatever entries the
remote server includes in its SSH_FXP_READDIR response, with no default
filtering. Whether '.'/'..' show up therefore depends entirely on the
remote SFTP server implementation, producing inconsistent results between
otherwise identically configured SFTP accounts.

Test plan:
1. prove t/db_dependent/Koha/File/Transport/SFTP.t
   => fails before this patch (listing includes '.' and '..')
   => passes after this patch
2. prove -r t/db_dependent/Koha/File/Transport/ t/db_dependent/Koha/File/Transports.t
   => all still pass

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-03 13:00:32 +00:00
Martin Renvoize 407e1fe360
Bug 43078: Include longname and perms keys in FTP's NLST fallback listing
The MLSD path, SFTP, and Local always return all six documented
list_files() keys (filename, longname, size, perms, mtime, type) - undef
where the value is unknown, but the key always present. The NLST
fallback path (servers without MLSD support) only set filename, size,
mtime and type, omitting longname and perms entirely rather than
setting them to undef.

Functionally harmless for consumers reading $file->{perms} (Perl
returns undef for a missing hash key too), but it breaks the "same flat
shape" contract this bug established, and would trip up any consumer
using exists() instead of defined() to check for a key.

Test plan:
1. prove t/db_dependent/Koha/File/Transport/FTP.t
   => All tests successful, including the new assertion that longname
      and perms keys are present (as undef) on the NLST fallback path.
2. koha-qa.pl -v 2 --more-tests
   => [PASS], no critic/pod warnings.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-03 13:00:31 +00:00
Martin Renvoize 76ff3af495
Bug 43078: Unify file transport list_files() across FTP, Local and SFTP
Net::SFTP::Foreign's ls() returned file metadata with size, permissions
and modification time nested inside a Net::SFTP::Foreign::Attributes
object, while FTP and Local returned flat hash keys and silently
dropped directories. Callers of list_files() needed backend-specific
parsing to get at file metadata or to know a directory even existed,
defeating the purpose of the shared transport API.

All three transports now return the same flat hashref shape per entry:
filename, longname, size, perms, mtime, type ('file'/'directory'/
'other'). Directories are included everywhere; '.' and '..' remain
excluded everywhere. FTP's MLSD path prefers the UNIX.mode fact for
exact Unix permissions, falling back to an approximation from the RFC
3659 perm capability fact when UNIX.mode isn't available, and parses
the modify fact into mtime; its NLST fallback (servers without MLSD)
probes each entry with SIZE/MDTM to recover size, mtime and a
best-effort type.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-03 13:00:30 +00:00
Owen Leonard 76167f6cfe
Bug 43177: Use archived link for Bridge Material Type Icon Set
This patch updates the About page in the section for the Bridge Material
Type Icon Set. The link is updated to use the last valid saved version
on the Internet Archive.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-03 13:00:29 +00:00
Martin Renvoize d2b4fd2709
Bug 42992: FTP file transport: fix list_files() corrupting filenames
Koha::File::Transport::FTP::_list_files parsed the LIST / "ls -l" output
with a non-greedy date capture that only consumed the first of the three
date tokens (the month), leaving the day and time glued to the front of
the filename. Every filename returned for an FTP transport was therefore
prefixed with the listing date, e.g.

    -rw-r--r-- 1 user group 1234 Jan 01 12:00 QUOTES_413514.CEQ
    => filename "01 12:00 QUOTES_413514.CEQ"

Callers (Koha::Edifact::Transport, marc_ordering_process.pl) then asked
the server for a file that does not exist, so no files were downloaded,
renamed or deleted over FTP. SFTP and Local transports were unaffected.

This regressed when the EDI transport was moved onto the file_transports
system (bug 38489). The previous Koha::Edifact::Transport::ftp_download
listed files with Net::FTP->ls (NLST, bare names); the refactor switched
to Net::FTP->dir (LIST) so it could return the richer hashref shape used
by the SFTP and Local transports, but the parse was faulty.

The date field in "ls -l" output is a fixed run of three whitespace
separated tokens (Mon DD HH:MM or Mon DD YYYY). Consume it explicitly so
the filename is captured cleanly, preserving spaces and still skipping
directories. The LIST-based approach and the filename/longname/size/perms
return shape are kept so the structure stays consistent across transport
classes (relied on by consumers, including out-of-tree ones).

Test plan:
1) prove t/db_dependent/Koha/File/Transport/FTP.t
2) git checkout Koha/File/Transport/FTP.pm and re-run: the list_files()
   subtest fails on the filename/size/perms assertions
3) prove t/db_dependent/Koha/Edifact/Transport.t

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-08-03 13:00:25 +00:00
Matt Blenkinsop d6d95f7d7c
Bug 42827: Restore values to the Items expected column in parcels.pl
Test plan:
1. Create an order with multiple items and receive the order in full
2. From the vendor record, click Receive shipments
3. You should be in parcels.pl and the table should show your shipments
4. The Items expected column will show 0 even though you ordered multiple items
5. Apply patch and restart_all
6. Refresh the page, the column should now show a value

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:35 +00:00
Martin Renvoize 5799159034
Bug 43082: Fix flaky object filter test in action_logs.t
The default `_match=contains` matching builds a LIKE query even on
integer columns, so a plain `?object=42` also matches object values
like 420 or 142. The test's dynamically-assigned patron borrowernumber
(auto_increment, never reset by transaction rollback) could drift into
a value containing "42" as a substring, intermittently inflating the
match count in CI. Add `_match=exact` since the test's intent is an
exact object id match.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:34 +00:00
David Cook bbc0124ede
Bug 43159: Restore localisation of PERL5LIB and KOHA_HOME
This change localises PERL5LIB and KOHA_HOME,
which means that any changes made to PERL5LIB or KOHA_HOME
within _do_instance() in koha-plack are confined to the specific
instance being worked on in that function.

See Bugzilla for steps on how to reproduce the problem.

Test plan:
- Apply the patch
- sudo vi debian/scripts/koha-plack

Within _do_instance after PERL5LIB is set, add the following:
echo $PERL5LIB

- sudo koha-create --request-db test1
- sudo ./debian/scripts/koha-plack --restart test1 test1 test1 test1
- Note that now PERL5LIB stays constant:

/usr/share/koha/lib:/usr/share/koha/installer:/usr/share/koha/lib/installer

- git restore debian/scripts/koha-plack

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:33 +00:00
Kyle M Hall 6d30ead414
Bug 43103: Switch OR chains to INs for batch item modification and patron deletion
Three searches pass large ID lists:
* Koha::BackgroundJob::BatchUpdateItem passes every itemnumber of the
  batch, and batch item modifications can cover tens of thousands of
  items
* Koha::Items::batch_update does the same with the modified
  itemnumbers when collecting biblionumbers for reindexing
* Koha::Patrons::filter_by_safe_to_delete does it with the
  borrowernumbers found safe to delete ( unlikely to approach
  the same numbers as items/bibs, but still good to fix )

This patch switches all three to IN. The lists are still passed as
values, but the query stays a flat IN list the optimizer handles well
instead of a chain of ORs that can't be optimized.

Test Plan:
1) Apply this patch
2) prove t/db_dependent/Koha/Items/BatchUpdate.t
3) prove t/db_dependent/Koha/Patrons.t
4) Note all tests pass!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:31 +00:00
Paul Derscheid 53524b4824
Bug 43174: (QA follow-up) Restore user choices on redraw independent of search state
To test:
- cypress run --spec t/cypress/integration/KohaTable/PatronSearch_spec.ts
- cypress run --spec t/cypress/integration/KohaTable/KohaTable_spec.ts

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:30 +00:00
Jonathan Druart 684c6cb96a
Bug 43174: Restore defer loading for patron searches
On the hold request and the patron search pages, the search is not
deferred when a state exists.

Coming from
  commit 2a34413601
  Bug 41604: Save user decision per column when visibility changes
-        if (default_save_state_search) {
+        if (default_save_state || default_save_state_search) {
             $("#" + settings.nTable.id).data("loaded_from_state", true);
-        } else {
+        }

+        if (!default_save_state_search) {
             delete state.search;
             state.columns.forEach(c => delete c.search);

I think this was a leftover from another try to fix bug 41604. I don't
see how it could help in the context of saving the user decision.
In the different patches from bug 41604 we don't test loaded_from_state.

Test plan:
Go to /cgi-bin/koha/members/members-home.pl
=> The table is not displayed
Search for patrons
=> The table is displayed
Reload the page
=> Without this patch the table is displayed
=> With this patch the table is not displayed

If you have "Save search state on page change" enabled for this table,
the table should be displayed if a previous search was made.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:29 +00:00
Jonathan Druart 96f1906fcc
Bug 43174: Add a Cypress test
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:27 +00:00
Jonathan Druart 21e34086ca
Bug 43053: Simplify code, only 1 return statement
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:26 +00:00
Matt Blenkinsop 2ad5ba09cb
Bug 43053: Fix display of vendor payment methods
Test plan:
1) Add multiple vendor payment method authorised values
2) Create a vendor and add multiple payment methods
3) Click to edit, the payment method will be displaying one option of a "|" concatenated string
4) Apply patch and run yarn js:build
5) Hard refresh the browser, the payment methods should now display correctly
6) Edit the payment methods and save, the saved changes should be correctly displayed on the vendor details page
7) Add a new vendor with multiple payment methods, it should save correctly

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:25 +00:00
Jonathan Druart 29e34ba1c2
Bug 43053: Add a Cypress test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:24 +00:00
David Nind 4b7da59db5
Bug 43173: Update the terminology for the log viewer - "cardnumber" to "card number"
Update the log viewer to change "Modify cardnumber" to
"Modify card number", as per the terminology guidelines
https://wiki.koha-community.org/wiki/Terminology#C

Test plan:
1. Edit a patron and change their card number under the library
   management section.
2. Go to Tools > Additional tools > Log viewer.
3. Note under the actions section that there is an action:
   Modify cardnumber
4. Click "Submit" to see the log viewer results.
5. Note that there is an entry for the patron card number change
   with the action: "Modify cardnumber".
6. Apply the patch.
7. Refresh the log viewer page.
8. Note that "Modify cardnumber" is now "Modify card number"
   in both places.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Gretchen <maxeinergl@gcc.edu>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:23 +00:00
Jonathan Druart 3573aa2492
Bug 43168: Add a test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:22 +00:00
Nick Clemens c13096326f
Bug 43168: Check if there are errors, not if variable defined
$errors is an array ref returned from a blank array variable

if( $errors ){ #this will be true

if( scalar @$errors ){ #only true when array has members

To test:
1 - tail -f /var/log/koha/kohadev/intranet-error.log
2 - Stage a marc file
3 - Note the error:
==> /var/log/koha/kohadev/intranet-error.log <==
[2026/07/23 19:11:37] [WARN] The following error(s) occurred during ISO2709 record import:
ERROR:  Koha::ImportBatch::try {...}  /kohadevbox/koha/Koha/ImportBatch.pm (105)
4 - Apply patch
5 - Restart all
6 - Stage a file
7 - No error!

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:20 +00:00
Pedro Amorim d5715ed753
Bug 42988: DBRev 26.06.00.010
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:19 +00:00
Pedro Amorim d9dda87fff
Bug 42988: dbic
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:18 +00:00
Kyle M Hall 73380b410c
Bug 42988: Default holds_get_captured to yes for new SIP accounts
The account editor preselects "No" for "Holds get captured" when
creating a new SIP account, but the SIP checkin default is to capture
holds. This patch makes the editor default the option to "Yes" so a
new account behaves the same as one that never set the option.

Test Plan:
1) Apply this patch
2) yarn js:build
3) Restart all the things!
4) Create a new account in the SIP2 administration module
5) Note "Holds get captured" defaults to "Yes"!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:17 +00:00
Kyle M Hall 48c54b04b6
Bug 42988: Update database, set holds_get_captured default to 1
The migration from SIPconfig.xml to the database ( bug 37893 ) leaves
holds_get_captured NULL for any account that didn't define it in the
config file. At SIP checkin an unset holds_get_captured defaults to
on, but the REST API returns NULL boolean columns as false, so the
SIP account editor shows "No" selected. Saving the account without
touching the option then stores 0 and holds are no longer captured
at SIP checkin.

This patch changes the column default to 1 and sets existing NULL
values to 1 so the stored value matches the actual checkin behavior.

Test Plan:
1) If you have no SIP accounts, create one
2) Simulate an account migrated from a SIPconfig.xml that doesn't define holds_get_captured:
   UPDATE sip_accounts SET holds_get_captured = NULL;
3) Edit the account, note "Holds get captured" shows "No" even though
   holds *are* captured at SIP checkin while the value is unset
4) Apply this patch
5) Run updatedatabase.pl
6) SELECT login_id, holds_get_captured FROM sip_accounts;
7) Note holds_get_captured is now 1!
8) Edit the account again, note "Yes" is now selected for "Holds get captured"!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:16 +00:00
Laura_Escamilla ad1d40f0b7
Bug 43134: Add missing colons to OPAC advanced search section headings
To test :
1. Apply the patch
2. Go to the OPAC advanced search page http://kohadev.mydnsname.org:8080/cgi-bin/koha/opac-search.pl
3. Click More options
4. Notice that all section titles now use colons
   - "Search for:" = colon
   - "Limit to any of the following:" = colon
   - "Location and availability:" = colon
   - "Sort by:" = colon
   - "Publication range" = colon
   - "Language" = colon
   - "Audience" = colon
   - "Content" = colon
   - "Format" = colon
   - "Additional content types..." = colon
5. Sign off and have an incredible day! :)

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:15 +00:00
Owen Leonard 32e16ca89a
Bug 43192: Cast escape_str(obj) arg to string to use escapeHtml func
The escape_str function in the OPAC requires the argument to be a string,
and will throw a fatal error if passed a Number. This is because
String.prototype.escapeHtml is created by Koha and is not a JS standard.
This patch converts the object to a string before applying escapeHtml.

To test, apply the patch and log in to the OPAC.

- If necessary, add a list.
- Search for records in the catalog and add one or more of the results
  to your new list.
- Go to Lists -> Your lists.
- In the list of your lists, click the "Delete" link for the list you
  added records to.
  - You should see a confirmation modal, "Are you sure you want to
   delete this list?" showing the name of the list and the number of
   items on it.
  - Clicking "Yes, delete" should delete the list.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-31 13:40:08 +00:00
Martin Renvoize 0f894cb6b1
Bug 41410: DBIx::Class schema update
Regenerated via `dbic --force` after adding the local_holdgroup_match
column to hold_fill_targets and tmp_holdsqueue in the previous patch.

Test plan:
  1. Inspect Koha/Schema/Result/HoldFillTarget.pm and TmpHoldsqueue.pm
     and confirm the new local_holdgroup_match accessor is defined
     above the `DO NOT MODIFY` marker.
  2. Confirm custom content below the marker (koha_object_class,
     +item_level_request is_boolean) is preserved.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-30 08:42:33 +00:00
Pedro Amorim cf852cdd10
Bug 41410: dbic
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:15 +00:00
Pedro Amorim 471571a17d
Bug 41410: DBRev 26.06.009
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:14 +00:00
Martin Renvoize 9b27b4d03d
Bug 41410: (follow-up) Fix local_holdgroup_match set incorrectly by LocalHoldsPriority
local_holdgroup_match was being hardcoded to 1 in the two LocalHoldsPriority
allocation paths (GiveLibrary and GiveLibraryGroup), regardless of whether the
chosen item actually satisfied the LocalHoldsExclusivity conditions.  This
caused staff to be directed to pull an item that would not trigger the hold at
checkin when the two features used different patron/item controls.

The fix removes the premature local_holdgroup_match => 1 from both LHP paths and
lets _flag_local_holdgroup_matches evaluate all allocated items using the correct
LocalHoldsExclusivity* sysprefs.

Also fixes a pre-existing missing bind-variable placeholder in the
INSERT INTO tmp_holdsqueue statement (17 columns, only 16 ?).

Test updates:
- Existing local_holdgroup_match test gains the matching LocalHoldsExclusivity
  sysprefs so the flag is set via the correct code path.
- New subtest reproduces the reported bug: LHP (HomeLibrary/homebranch) targets
  a non-local item while exclusivity (PickupLibrary/holdingbranch) should not
  flag it — confirms local_holdgroup_match is 0 after the fix.

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Sponsored-by: Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:13 +00:00
Martin Renvoize 4b23e5c5d1
Bug 41410: (follow-up) Decouple exclusivity from LocalHoldsPriority
LocalHoldsExclusivity and LocalHoldsPriority are adjacent but distinct
features: priority alters *which patron's hold* gets filled first, while
exclusivity restricts *which items* may fill a hold during a time window.
Each should be usable independently of the other.

Changes:
- Rename LocalHoldsPriorityExclusivityPeriod to LocalHoldsExclusivityPeriod
- Add LocalHoldsExclusivityPatronControl (PickupLibrary/HomeLibrary)
- Add LocalHoldsExclusivityItemControl (homebranch/holdingbranch)
- HoldsQueue: add _flag_local_holdgroup_matches() that flags
  local_holdgroup_match on allocated items using hold-group matching
  and the new exclusivity control prefs, regardless of LocalHoldsPriority
- Reserves: CheckReserves exclusivity check now computes its own
  "is this item local" using the new prefs and hold-group siblings,
  independent of the LocalHoldsPriority matching variables
- Atomicupdate handles both fresh install and rename-from-old-pref paths
- Pref UI note clarifies independence from LocalHoldsPriority

Test plan:
1. Apply patch, run updatedatabase.pl
2. Verify three new sysprefs exist in Administration > System preferences
   > Circulation: LocalHoldsExclusivityPeriod, LocalHoldsExclusivityPatronControl,
   LocalHoldsExclusivityItemControl
3. Set LocalHoldsPriority to "Don't give" (None)
4. Create a library hold group with at least two libraries (e.g. Lib-A, Lib-B)
5. Create a third library (Lib-C) NOT in the hold group
6. Create an item at Lib-B
7. Place a hold for a patron at Lib-A (pickup at Lib-A)
8. Set LocalHoldsExclusivityPeriod to 7, PatronControl to HomeLibrary,
   ItemControl to homebranch
9. Run the holds queue builder (misc/cronjobs/holds/build_holds_queue.pl)
10. Verify hold_fill_targets.local_holdgroup_match = 1 for that hold
    (item at Lib-B is in the same hold group as patron at Lib-A)
11. Check in the item at Lib-C — it should NOT fill the hold (exclusivity
    blocks the non-local item within the 7-day window)
12. Check in the item at Lib-B — it SHOULD fill the hold (local match)
13. Reset: delete the hold, create a new one with a patron at Lib-C
    (pickup at Lib-C). Run the queue again.
14. Verify hold_fill_targets.local_holdgroup_match = 0 for this hold
15. The item at Lib-B should now fill this hold at Lib-C checkin
    (non-local patron, no exclusivity protection)
16. Set LocalHoldsExclusivityPeriod to 0 — verify non-local items can
    fill holds normally regardless of hold_fill_targets flag
17. prove t/db_dependent/Holds/LocalHoldsPriority.t — passes
18. prove t/db_dependent/HoldsQueue.t — passes
19. prove t/db_dependent/Reserves.t — passes

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:12 +00:00
Martin Renvoize bec5c357b4
Bug 41410: Unit tests
Covers the new LocalHoldsPriorityExclusivityPeriod behaviour and keeps
existing tests working with the schema change:

 - t/db_dependent/Holds/LocalHoldsPriority.t: new subtest exercising
   all four cases -- pref disabled, pref active on a fresh hold, pref
   active past the window, and pref active with no hold_fill_targets
   row (queue hasn't run).
 - t/db_dependent/HoldsQueue.t: assert that after CreateQueue() with a
   local-group match, both tmp_holdsqueue and hold_fill_targets rows
   carry local_holdgroup_match = 1.
 - t/db_dependent/Reserves.t, t/db_dependent/Koha/Hold.t: qualify the
   two direct "INSERT INTO hold_fill_targets VALUES (...)" statements
   with explicit column names so they continue to work now that
   hold_fill_targets has one extra column.

Test plan:
  prove t/db_dependent/Holds/LocalHoldsPriority.t \
        t/db_dependent/HoldsQueue.t \
        t/db_dependent/Reserves.t \
        t/db_dependent/Koha/Hold.t

All tests should pass.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:10 +00:00
Martin Renvoize a2ef73fe95
Bug 41410: Add local holds priority exclusivity period
When LocalHoldsPriority is enabled, the holds queue picks a local-group
item for eligible holds.  Until now there was nothing to reserve that
priority at checkin time: if a non-local item was returned first, it
would happily fill the hold and the local-priority decision was lost.

This patch adds a time-bounded exclusivity window so that, for a
configurable number of days after a hold is placed, a hold whose queue
target is a local-group item will not be filled by a non-local item.

Changes:
 - C4/HoldsQueue.pm: in MapItemsToHoldRequests, flag %item_map entries
   that matched on local library (GiveLibrary/GiveLibraryAndGroup) or
   local hold group (GiveLibraryGroup/GiveLibraryAndGroup) with
   local_holdgroup_match => 1, and propagate that into both the
   tmp_holdsqueue and hold_fill_targets INSERTs.
 - C4/Reserves.pm: in the CheckReserves loop, when
   LocalHoldsPriorityExclusivityPeriod is set, the hold is still within
   that window, and the item being evaluated is not itself a local
   match, skip the reserve if its hold_fill_targets row has
   local_holdgroup_match = 1.  Missing hold_fill_targets row means "no
   information" -- fall through so holds are never trapped between
   queue runs.
 - circulation.pref: expose the new LocalHoldsPriorityExclusivityPeriod
   preference next to the existing LocalHoldsPriority* entries.

The feature is a no-op unless both LocalHoldsPriority is set to
something other than "Don't give" AND
LocalHoldsPriorityExclusivityPeriod is > 0.

Test plan:
 1) Apply the patches and run the atomicupdate.
 2) Set up a library group flagged as a local hold group containing
    two libraries (call them A and B).  Add a third library C that is
    NOT in the group.
 3) Add one item to each library (itemA, itemB, itemC) on the same
    biblio.
 4) Administration -> System preferences -> Circulation:
    - Set LocalHoldsPriority to "Give library group".
    - Set LocalHoldsPriorityPatronControl to "home library".
    - Set LocalHoldsPriorityItemControl to "home library".
    - Set LocalHoldsPriorityExclusivityPeriod to 7.
 5) As a patron whose home library is A, place a title-level hold with
    pickup at A.
 6) Run the holds queue (misc/cronjobs/holds/build_holds_queue.pl).
    -> The queue should target itemA (or itemB) for this hold.
    -> Confirm in the database:
         SELECT reserve_id, itemnumber, local_holdgroup_match
           FROM hold_fill_targets;
       The targeted row must have local_holdgroup_match = 1.
    -> Holds to pull / holds queue report should also show the flag
       (tmp_holdsqueue.local_holdgroup_match = 1).
 7) At library C, check in itemC (the non-local item).
    -> The hold must NOT be captured -- itemC simply goes back on the
       shelf.
 8) At library A, check in itemA (the local item).
    -> The hold IS captured for the patron.
 9) Cancel and re-place the hold, run the queue again.  Manually age
    the hold past the window:
         UPDATE reserves
            SET reservedate = DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY)
          WHERE reserve_id = <id>;
    Now check in itemC again.
    -> The hold IS captured (exclusivity window has expired).
10) Re-place the hold but do NOT run the queue.  With no
    hold_fill_targets row for the reserve, check in itemC.
    -> The hold IS captured (we never trap holds between queue runs).
11) Flip LocalHoldsPriorityExclusivityPeriod back to 0 and repeat
    step 7.
    -> The hold IS captured; the feature is fully disabled.
12) Run:
      prove t/db_dependent/Holds/LocalHoldsPriority.t \
            t/db_dependent/HoldsQueue.t \
            t/db_dependent/Reserves.t \
            t/db_dependent/Koha/Hold.t
    -> All tests pass.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:09 +00:00
Martin Renvoize 753c5fe349
Bug 41410: Add local_holdgroup_match column and exclusivity period syspref
Adds groundwork for a time-bounded local holds exclusivity feature:

- Adds a local_holdgroup_match flag (tinyint, default 0) to both
  hold_fill_targets (consulted by CheckReserves/_Findgroupreserve at
  checkin) and tmp_holdsqueue (so queue/pull-list reports can display
  it). The flag will be set by the queue builder when a local-group
  match is made.
- Adds a new system preference LocalHoldsPriorityExclusivityPeriod
  (Integer, default 0 = disabled) specifying the number of days from
  hold creation during which only locally-targeted items may fill a
  hold with a local queue target.
- Provides an atomicupdate that guards both ALTERs with column_exists
  and INSERT IGNOREs the new syspref.

Test plan:
  1. Apply atomicupdate: perl installer/data/mysql/updatedatabase.pl
  2. Verify both columns exist:
       SHOW COLUMNS FROM hold_fill_targets LIKE 'local%';
       SHOW COLUMNS FROM tmp_holdsqueue LIKE 'local%';
  3. Verify syspref row:
       SELECT variable, value FROM systempreferences
         WHERE variable = 'LocalHoldsPriorityExclusivityPeriod';
  4. Confirm default value is 0 and type is Integer.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:08 +00:00
Jan Kissig 2fe11906a7
Bug 42292: (QA follow-up) replace remaining OD_password_required with Koha.prefs.OverDrivePasswordRequired
As in opac-user.tt OD_password_required was replaced by Koha.prefs.OverDrivePasswordRequired. The script overdrive.js checks already for the Koha.prefs variable.

1) Set sysprefs: OPACOverDrive, OverDriveCirculation, OverDrivePasswordRequired
= Enable/Yes; OverDriveClientKey, OverDriveClientSecret, OverDriveLibraryID =
any non-empty value (e.g. 123).
2) Go to <opac_url>/cgi-bin/koha/opac-user.pl, click "Log in to your OverDrive
account" - password modal appears (correct).
3) Go to <opac_url>/cgi-bin/koha/opac-overdrive-search.pl?q=music, click the
same link, it fails with "No password provided" instead, no modal.

4) apply patch and repeat 3) The modal appears now.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:07 +00:00
Jan Kissig c2514018b4
Bug 42292: (QA follow-up) remove unused reference OverDriveEnabled in opac-shelves
As OverDriveEnabled is declared via SET in the template and not a system preference and after all not used in that script it is removed.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:06 +00:00
Jan Kissig 87d4eac98e
Bug 42292: (QA follow-up) Fix evaluation of OpacHighlightedWords to be numeric in opac-results
OpacHighlightedWords was set in quotation marks and resulted in either "1" or "0".
As "0" and "1" always evaluates to true in JS the following error was thrown when OpacHighlightedWords was set to Don't highlight: Uncaught TypeError: $(...).highlight is not a function

To test:
a) Set system preference OpacHighlightedWords to Don't highlight
b) search a title in the opac with open dev console and check that no error (like above) is thrown.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:05 +00:00
Paul Derscheid 71b121bb6f
Bug 42292: (follow-up) Fix OverDrive/OpenLibrary references in opac-results.tt
Three issues introduced by the OPAC port of opac-results.tt:

- The tt-allowed block sets OverDriveEnabled via
  `Koha.Preference("OverDriveEnabled")`, but OverDriveEnabled is a
  TT-side SET variable (computed from OverDriveLibraryID +
  OverDriveClientKey + OverDriveClientSecret), not a system
  preference, so Koha.prefs.OverDriveEnabled is always "".
  The code at line 621 then reads an unprefixed `OverDriveEnabled`
  identifier which is not declared -- the OverDrive result block
  never fires.
- The neighbouring block `if (OpenLibrarySearch) { ... }` reads an
  unprefixed identifier for a value that is only exposed on
  Koha.prefs, so the OpenLibrary result block also never fires.
- Both blocks build a throbber <img> with
  `src=\`${interface}/${theme}/images/spinner-small.gif\`` inside a
  single-quoted JS string; the backticks are literal characters
  there, so the rendered src ends up as that literal template
  text, not a usable URL.

Replace the OverDriveEnabled addPrefs entry with a plain
`const OverDriveEnabled = [% OverDriveEnabled ? 1 : 0 | html %];`
that reads the TT SET variable, prefix OpenLibrarySearch with
Koha.prefs, and rebuild the throbber src via normal string
concatenation.

To test:
- With OverDrive configured (OPACOverDrive, OverDriveLibraryID,
  OverDriveClientKey, OverDriveClientSecret), run a search in the
  OPAC: the OverDrive results panel shows the spinner and then
  the API results.
- With OpenLibrarySearch enabled, the OpenLibrary panel does the
  same.
- In both cases the spinner image loads (no broken-image icon).
- Disable both prefs -> neither panel is rendered, no console errors.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:04 +00:00
Paul Derscheid b4035c5a0c
Bug 42292: (follow-up) Fix duplicate NovelistSelectProfile declaration
In the new tt-allowed block, the NovelistSelect fallback declarations
redeclare `NovelistSelectProfile` instead of also exposing
`NovelistSelectPassword`:

    var NovelistSelectProfile = "[% NovelistSelectProfile | html %]";
    var NovelistSelectProfile = "[% NovelistSelectPassword | html %]";

So `NovelistSelectProfile` ends up holding the password, and
`NovelistSelectPassword` is never declared. The later
`novSelect.loadContentForQuery(..., NovelistSelectProfile,
NovelistSelectPassword, ...)` call then throws ReferenceError and
NovelistSelect content never loads on biblio detail pages.

Rename the second declaration to `NovelistSelectPassword` to match
the original template behaviour.

To test:
- Configure the NovelistSelect sysprefs (NovelistSelectEnabled,
  NovelistSelectProfile, NovelistSelectPassword) in the OPAC. Real
  NovelistSelect content only loads with valid EBSCO subscription
  credentials -- with dummy values the JS path still runs but the
  tab stays empty; that is still sufficient to verify this fix.
- Open an OPAC biblio detail page with a known ISBN.
- In the browser console, run
  console.log(NovelistSelectProfile, NovelistSelectPassword)
  and confirm both values are printed (before the fix,
  NovelistSelectPassword is undefined).
- With valid subscription credentials, NovelistSelect content loads
  in its tab/panel instead of throwing a ReferenceError in the
  console.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:03 +00:00
Paul Derscheid a8da9a5f14
Bug 42292: (follow-up) Rename opac-shelves print flag to avoid shadowing window.print
The port declares `var print = [% print ? 1 : 0 | html %];` at the
top of the tt-allowed block, then later calls `window.print()` inside
`if (print) { ... }`. Declaring `var print` at top-level overwrites
the built-in `window.print` with 0 or 1, so the subsequent
`window.print()` call throws "print is not a function" and the print
view never fires its actual print dialog.

Rename the flag to `should_print` and update the single `if (print)`
check accordingly.

To test:
- Open a public list with items in the OPAC.
- Click "Print this list" (or load the page with op=print).
- The browser print dialog opens and the window closes afterwards.
- No "print is not a function" error in the console.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:02 +00:00
Paul Derscheid ed7fef7dd7
Bug 42292: (follow-up) Replace removed dateformat_pref var in OPAC calendar.inc
The OPAC port moved the dateformat pref into Koha.addPrefs and dropped
the local `var dateformat_pref = ...;` declaration, but four usages of
`dateformat_pref` remain in the same file (the debug alert, two
`switch` statements, and the get_dateformat_str call in validate_date).
At runtime the switches throw ReferenceError, so flatpickr ends up
with the default 'Y-m-d' format and date validation via validate_date
crashes before showing the invalid-date alert.

Replace all remaining occurrences with Koha.prefs.dateformat, which is
populated by the new addPrefs block at the top of the include.

To test:
- Set dateformat syspref to each of us / metric / dmydot / iso.
- Open any OPAC page using a flatpickr input (e.g. hold date, patron
  dob on the registration form).
- Confirm the widget renders dates in the expected format and
  entering an invalid string triggers the validate_date alert.
- No ReferenceError in the browser console on page load.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:01 +00:00
Jonathan Druart e53b7346c9
Bug 42292: Tidy all script tags - OPAC (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:50:00 +00:00
Jonathan Druart 5e822ff613
Bug 42292: Tidy all script tags - OPAC
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the OPAC module.

Non-trivial changes, we modify the whole OPAC in a single patch.
Testing this patch should focus on:
* date formatting
* the cover images on the search result and shelves views

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:49:59 +00:00
Jan Kissig 8812de46fe
Bug 42279: (QA follow-up) Fix straying TT variable, auto tidy
Removes a left over TT variable from a JS script block without data-tt-allowed="true" attribute. Auto tidy

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:49:58 +00:00
Paul Derscheid 3dbe00ccbe
Bug 42279: (follow-up) Restore numeric compare of quantityrec
The port quoted [% quantityrec | html %] into a string const, but
the value is used as `$(this).val() < quantityrec` where both sides
are now strings and JS does a lexicographic compare ("10" < "5" is
true), giving the wrong branch when the user enters a multi-digit
quantity.

Render quantityrec as a JS number (with a 0 fallback) so the
comparison stays numeric, matching the original template output.

To test:
- Modify an existing order with quantityrec, e.g. 5 items received.
- Change quantity to 10 (higher than received): "can't add new item"
  alert fires, value reverts.
- Change quantity to 2 (lower): "don't forget to delete" alert fires.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:49:57 +00:00
Jonathan Druart af445bdeba
Bug 42279: Tidy all script tags - acqui (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:49:56 +00:00
Jonathan Druart 3113ca088e
Bug 42279: Tidy all script tags - acqui
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the acquisition module.

The 2 main changes are in the following files:
* basket.tt
It's mostly about table settings and the visibility of columns depending
on syspref:
  - "supplier report" / EDIFACT
  - "modify" / if basket is not closed
  - "cancel" / CancelOrdersInClosedBaskets
* neworderempty.tt
trivial changes, no error or warning in the console should appear.
Confirm than createing a new order works as expected.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:49:55 +00:00
Owen Leonard 707878312b
Bug 41116: (follow-up) Fix template variable filter
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:49:54 +00:00
Owen Leonard 56928506ae
Bug 41116: (follow-up) Some more fixes for consistency
- There was a mix of "patronphone" and "patron-phone" and I fixed it to
  standardize on "patron-phone" (and patron-email) which I find easier
  to read.
- I made similar changes to the branchtransfers template
- Corrected the TT filter on mailto links from "html" to "uri"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:49:53 +00:00
Andrew Fuerste Henry 1b2dd6ab54
Bug 41116: Standardize selectors in patron contact information in hold found modals
This patch modifies both hold-found-modal and hold-found1. It adds a
class to the patron phone number. For patron email, it changes the
existing id to a class, expands the selector to include the bullet
point in hold-found-modal, and standardizes the name (changing from
"#boreamil' to ".patronemail").

To test:
1 - have a patron with both a phone number and an email address
2 - create a hold for your patron
3 - check an item in to fill your hold, triggering the hold-found-modal
4 - inspect the patron contact info, confirm there is no class on the
    phone number
5 - confirm there is an id on the patron email but it does not include
    the bullet point and is inconsistent with the names of other classes
    in the modal
6 - click Confirm Hold
7 - check your item in again, triggering the hold-found1 modal
8 - confirm there is no class on the patron phone number
9 - confirm there is an id on the patron email with an inconsistent
    name
10 - revert the waiting status of your hold
11 - apply patch, restart_all
12 - check your item in again, triggering hold-found-modal
13 - confirm the patron phone now has a class, .patronphone
14 - confirm the patron email now has a class, .patronemail, that the
     class includes the bullet point, and that the #boremail id has been
     removed
15 - click Confirm Hold
16 - check your item in again, triggering hold-found1 modal
17 - confirm the patron phone now has a class, .patronphone
18 - confirm the patron email now has a class, .patronemail, and that
     the #boremail id has been removed

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:49:52 +00:00
Phil Ringnalda 67da9d31ec
Bug 36897: Make it possible to use select[multiple] and select[size] in a sidebar
Currently, if you put either <select multiple> or <select size="10"> in
an <aside><fieldset> sidebar, you get a height of "one fat line" rather
than the bigger box you would expect.

Test plan:
 1. Without the patch, load Reports - Use saved
 2. Open Firefox developer tools to the Inspector tab
 3. With the "Pick an element from the page" tool, click on the word
    "Filter" in the Reports page's sidebar
 4. Just above that line in the HTML display is a <fieldset
    class="brief"> - right click that and choose Edit As HTML
 5. After the fieldset element, paste

<select multiple><option>one</option><option>two</option></select><select size="10"></select>

 6. Click outside the editor to save your changes
 7. Note that both new selects are only one line high
 8. Apply patch, run yarn build, Shift+reload to clear your cache
 9. Repeat steps 3 through 6
10. Note that both selects are more than one line high
11. Set the preference EnableAdvancedCatalogingEditor to Enable
12. Cataloging - Advanced editor
13. In the left sidebar, verify that the Clipboard is still 10 lines high,
    not one fat line like it was before bug 36589

Sponsored-by: Chetco Community Public Library
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:49:51 +00:00
Jan Kissig 84948a6041
Bug 40201: Add addidtional fields to subscription search on serials home
This patch adds additional fields (table subscription, searchable) to the subscription search form on serials home.

To test (ktd):
a) create some additional fields in Admin -> Additional fields -> Serials subscriptions. Make sure the 'Searchable' checkbox is checked.
b) go to serials home: More -> Serials
c) Check that the form is missing the additional fields just defined.
d) apply patch and call restart_all
e) reload serials home and check that the search form is now extended with the additional fields

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-29 15:49:44 +00:00
Owen Leonard 7dfa99f237
Bug 42118: Update various admin pages to use grid layout for forms - Part 2
This patch updates several more administration templates with new markup
and CSS for layout forms using CSS grids.

To test, apply the patch rebuild the staff interface CSS.

Check the following pages, confirming that the "New" and
"Edit" versions of the form look correct in each case:

- Administration -> Item search fields
- Administration -> OAI sets
  * Updated OAI set mappings form to add maxlength attributes to field
    and subfield inputs.
- Administration -> OAI repositories
- Administration -> SMS cellular providers
- Administration -> SMTP servers

Sponsored-by: Athens County Public Libraries
Signed-off-by: Felicie <felicie.thiery@biblibre.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:33:03 +00:00
Jan Kissig 415d1bf087
Bug 42284: (QA follow-up) Fix missing query param index
This restores the param "index" to the popup window URL.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:33:02 +00:00
Jan Kissig 848dce7c95
Bug 42284: (QA follow-up) biblionumber must not be null
Presetting biblionumber to null (when adding a new record) will lead to a query parameter biblionumber=null when performing a z39.50 search followed by  importing a result. This patch will default biblionumber to "".

Testplan:
a) go to More -> Cataloging -> New Record ->  Z39.50/SRU search
b) search for a title f.e. "Programming Perl"
c) import one of the results
d) check that the opened URL has a param biblionumber=null and the addbiblio page displays an error:  The record you are trying to edit doesn't exist.
Add a new record or do a catalog search.
e) apply patch and redo steps a) - d) and check that query param biblonumber is 0 and addbiblio loads correct carrying the imported data.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:33:01 +00:00
Jan Kissig e30141ac7c
Bug 42284: (QA follow-up) Add missing Koha.prefs
Fix call of marcflavour which is saved in Koha.prefs store.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:59 +00:00
Paul Derscheid 3e3708f65b
Bug 42284: (follow-up) Restore hide_marc polarity for marc tag visibility
The original UNLESS/ELSE block kept MARC tag links visible by
default and hid them only when the hide_marc syspref was enabled:

    [% UNLESS Koha.Preference("hide_marc") %]
        toggleMARCTagLinks(true)
    [% ELSE %]
        toggleMARCTagLinks(false);
    [% END %]

The JS port inverted the test without swapping the branches, so
when hide_marc was off the tag links were hidden and when it was
on they were shown -- the opposite of the pref's intent.

Flip the condition back (!Koha.prefs.hide_marc).

To test:
- Set hide_marc = 0: open addbiblio.pl, MARC tag numbers and
  subfield codes are visible on first load.
- Set hide_marc = 1 and clear the marctags cookie (devtools ->
  Application -> Cookies): reload addbiblio.pl, tag numbers and
  subfield codes are hidden on first load.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:58 +00:00
Paul Derscheid bdc88607e1
Bug 42284: (follow-up) Use z3950_mandatory name as query param, not marc_lib
GetMandatoryFieldZ3950 (in cataloguing/addbiblio.pl and
authorities/authorities.pl) populates subfield_loo.z3950_mandatory
with the z3950 query-field name expected by z3950_search.pl
(isbn, title, author, issn, lccn, ...). The pre-port JS copied
this value into mandatories_label and used it as the URL parameter
name in GetZ3950Terms.

The BIG_LOOP rewrite stored subfield_loo.marc_lib as `label`
instead. marc_lib is the human-readable field label ("Title
statement"), so GetZ3950Terms started emitting URLs like
&Title+statement=... which z3950_search.pl ignores, and the
z3950 lookup from the editor stopped matching any subfield.

Use subfield_loo.z3950_mandatory for the label in
z3950_mandatory_subfields on both templates.

To test:
- Open addbiblio.pl and fill the ISBN / title / author subfields.
- Click "Z39.50/SRU search".
- In the popup form (or the network request), the pre-filled
  fields should correspond to the entered values (title -> Title,
  isbn -> ISBN, ...). Before this patch they were empty.
- Same flow from authorities/authorities.pl on a matching
  authority type.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:57 +00:00
Paul Derscheid 0cd30ad0e0
Bug 42284: (follow-up) Fix important_fields push and id concatenation
The BIG_LOOP rewrite introduced two regressions in the mandatory /
important field collector at the top of addbiblio.tt:

- `important_fields.push([ tag: ..., id: ..., ... ])` wraps the
  object in brackets instead of braces. `[a: 1]` is not a valid
  array literal, so the parser aborts the whole script tag and
  AreFieldsNotOk / PopupZ3950 never run for any record whose
  framework marks fields as "important".
- The `id` interpolation joins index and random with a literal `$`
  (`"[% index %]$[% random %]"`), but the DOM ids rendered
  elsewhere in the template (and looked up by AreFieldsNotOk via
  `divid = "tag_" + tag + "_" + id`) concatenate them with no
  separator.

Use `{...}` for important_fields and drop the `$` so the collector
shape and ids match the rest of the template.

To test:
- Open addbiblio.pl for a framework that has mandatory and
  important fields (e.g. default MARC21).
- Submit a new record with mandatory subfields empty: the error
  panel lists them and focuses the first empty input.
- Submit with only important subfields empty: the warning panel
  (not blocking) lists them under "The following important
  subfields aren't filled".
- Browser console has no SyntaxError on page load.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:56 +00:00
Jonathan Druart 3560c28bd0
Bug 42284: Tidy all script tags - addbiblio/authorities (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:55 +00:00
Jonathan Druart 5848c185f2
Bug 42284: Tidy all script tags - addbiblio/authorities
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the edition of biblio and authority records.

We rethink how (the ugly) BIG_LOOP works:
* push all mandatory and important fields in a JS array
* use objects/hashes instead of array to store/access tag, id, index and
  tab (vs arr[0], arr[1], ...)

For testing you need to focus on the mandatory and important fields and
subfields. Confirm that there is no change in behaviour introduced by
this patch.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:54 +00:00
Jan Kissig 043396c8ae
Bug 42293: (QA follow-up) Fix hiding of additiona fields in serials-search
This fixes the hiding of additional fields in mana search by generating an array of additional field ids instead of calling To.json(additional_fields_for_subscription) which led to an Template process error.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:53 +00:00
Jonathan Druart b54bfa2670
Bug 42293: Tidy all script tags - serials (auto tidy)
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:52 +00:00
Jonathan Druart 2ecbac6060
Bug 42293: Tidy all script tags - serials
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the serials module.

The 3 main changes are in the following files:
* routing.tt
* serials-search.tt
* subscription-detail.tt

Changes are trivial.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:50 +00:00
Hammat Wele 8d9d749c53
Bug 41988: (follow-up) Fix opac-news-rss page title
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:49 +00:00
Hammat Wele 30cc4af870
Bug 41988: OPAC News RSS links with HTML not working
The news RSS link on the OPAC appears to be invalid when the news
content contains HTML. Users cannot subscribe to the feed using
standard RSS readers or email clients.

To reproduce:

1- Go to Tools -> HTML customizations -> News
2- Click New Entry with the following settings:
   2-1- Display location: OPAC
   2-2- Publication date: today
   2-3- Title: "News test"
   2-4- Content: click on <> and paste some HTML code, for example:
        <span style="color: #ba372a; font-size: 12pt;">Le Pr&ecirc;t
        entre biblioth&egrave;ques est mis en pause.
        D&eacute;sol&eacute;s de
        l'inconv&eacute;nients.<br />Merci de votre
        compr&eacute;hension.</span>
3- Save the entry.
4- Go to the OPAC and copy the "RSS feed for system-wide library news"
   link.
5- Execute: curl -v
   http://opac/cgi-bin/koha/opac-news-rss.pl?branchcode= and copy the
   XML output to https://validator.w3.org/feed/ to check the feed.
     ==> The feed is not valid.
6- Try to create a subscription to this link in an email client (e.g.,
   Thunderbird).
     ==> It does not work, giving the error "Feed URL is not valid".
7- Apply the patch.
8- Repeat steps 5 and 6.
     ==> The feed is now a valid RSS.
9- Repeat step 7.
     ==> You can now subscribe to the RSS feed using standard readers
         and email clients.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:48 +00:00
Jan Kissig 08a906d95b
Bug 41681: (follow-up) Fix record number count when broken records are processed
When running the script bulkmarcimport.pl, the number of MARC records reported at the end of the execution does not match the actual number of records processed from the input file.

This patch fixes 3 issues
- broken (invalid) records from batch get now counted (record_number)
- when using the number param (n) to limit the number of records to be processed valid and invalid records get counted
- when using th offset param (o) the scipt errored when broken records were part of the offset.

To test:

1. Download the MARC file with the broken record
2. Run the following import commands and compare the counted import numbers:
  a) all records
     perl misc/migration_tools/bulkmarcimport.pl -b -v -m MARCXML --file 2_records_1st_broken.xml
     1 MARC records done in 0.0421221256256104 seconds
  b) limit to 1 record in total
     perl misc/migration_tools/bulkmarcimport.pl -b -v -n 1 -m MARCXML --file 2_records_1st_broken.xml
     1 MARC records done in 0.0420489311218262 seconds
  c) limit to 1 record  offset 1 record
     perl misc/migration_tools/bulkmarcimport.pl -b -v -n 1 -o 1 -m MARCXML --file 2_records_1st_broken.xml
     :2: parser error : Opening and ending tag mismatch: leader2 line 2 and leader
  <leader2>00216nam a22001097a 4500</leader>

3. Apply the patch
4. Repeat steps in 2 and compare the results
  a) 2 MARC records done in 0.0405838489532471 seconds
  b) 1 MARC records done in 0.00948596000671387 seconds  *
  c) 1 MARC records done in 0.0294840335845947 seconds

*though this one was skipped

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:46 +00:00
Hammat Wele f5ff5269ac
Bug 41681: bulkmarcimport.pl reports an incorrect number of MARC records processed
When running the script bulkmarcimport.pl, the number of MARC records reported at the end of the execution does not match the actual number of records processed from the input file.

To test:

1. Download the joined MARC file containing 5 records.
2. Run the import command:
   ./misc/migration_tools/bulkmarcimport.pl -b --file=marc_file.mrc
3. Check the final script report
   ==> 11 MARC records done in 0.566349983215332 seconds
   Expected output should show 5 records
4. Apply the patch
5. Repeat step 2, 3
   ==> The correct number is shown

Signed-off-by: Janusz Kaczmarek <januszop@gmail.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:45 +00:00
Emily Lamancusa 0aa5820916
Bug 40492: Improvements to offline circ actions table
Update the pending offline circulation actions page to link each item
barcode directly to the item details, rather than the biblio record, and
open the links in a new tab.

Add a column to show the last seen date of the items being processed,
with a warning if the item's last seen date is more recent than the
offline circulation action date.

To test:
1. Upload a Koha Offline Circulation (koc) file
   (the file attached to this bug can be used if you are using a
   sandbox or KTD with the standard sample data)
   i. Go to Circulation > Upload offline circulation file
   ii. Upload the file
   iii. Click "Add to offline circulation queue"
2. Go to Circulation > Pending offline circulation actions
3. Click on the item barcodes in the table
--> Note that each barcode links to the biblio record, and navigates
    away from the page within the same tab.
4. Apply patch and restart_all
5. Return to (or refresh) the pending offline circulation actions page
   and click on the item barcodes again
--> The barcodes now open the item details page in a new tab
--> There is a new column for the item last seen date
6. Without processing the offline circulation actions, check in one of
   the items listed in the table
7. Return to the Pending offline circulation actions page
--> Confirm that the last seen date of the item you just checked in is
    highlighted in red with a warning icon, since its last seen date is
    newer than the offline transaction date
8. Hover over the warning icon
--> Confirm that a tooltip appears with an explanation of why the date
    is in red

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:44 +00:00
Baptiste Wojtkowski 7cda9f0648
Bug 39729: Add option -exact_days to filter_by_last_update
This patch adds an option -exact_days, to filter objects by an exact
number of days.

To test:
1 - Apply test patch
2 - Run tests -> they fail
3 - Apply patch
4 - Run tests -> they don't fail

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:43 +00:00
Baptiste Wojtkowski 501d4c5769
Bug 39729: Add tests
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:42 +00:00
Nick Clemens 2d73f3eb42
Bug 42714: (QA follow-up) Add comment with Perl doc for bash
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:40 +00:00
Martin Renvoize ad5fe0adff
Bug 42714: (QA follow-up) Add tests for cronlogaction and Koha::Script logging
t/db_dependent/Log.t
- Add subtest verifying cronlogaction() prefixes loginfo with \$0
  (the running script path), appends the info parameter, defaults the
  action to 'Run', and respects custom action values.

t/Koha/Script.t
- Assert that \$Koha::Script::_cron is false when the module is loaded
  without the -cron flag, confirming INIT/END blocks will not log.

t/Koha/Script_cron.t
- Assert that \$Koha::Script::_cron is true when the module is loaded
  with the -cron flag, confirming INIT/END blocks will log.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:39 +00:00
Martin Renvoize ece2d64e76
Bug 42714: (QA follow-up) Limit auto-logging to -cron scripts
The INIT/END blocks in Koha::Script fire for every consumer of the
module, including non-cron admin and migration tools. Logging those as
CRONJOBS when CronjobLog is enabled would pollute the action log with
entries for tools that are not scheduled jobs.

Introduce a package-level $_cron flag that is set by import() when the
-cron argument is passed (import() runs at compile time, before INIT
blocks execute, so the flag is in the correct state when INIT fires).
Guard both the INIT and END blocks behind this flag so only scripts that
explicitly declare themselves as cron scripts receive automatic logging.

Also update the import() POD to document the auto-logging behaviour.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:38 +00:00
Martin Renvoize 1652191b7f
Bug 42714: (QA follow-up) Use \$0 instead of caller() in cronlogaction
caller(1) was introduced to skip past Koha/Script.pm when cronlogaction
is called from the INIT/END blocks, but it breaks any direct caller that
invokes cronlogaction from within a Try::Tiny try/catch closure or a
callback sub - in those contexts caller(1) resolves to Try/Tiny.pm (or
the calling module) rather than the script itself.

\$0 is always the path of the running script regardless of call depth,
INIT/END phase, or enclosing closure, making it the correct choice here.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:37 +00:00
Nick Clemens 8f5c0abd09
Bug 42714: Adjust cronlogaction to use caller(1)
Since we are calling from within another module we need more caller information to
log the correct script

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:35 +00:00
Nick Clemens b9ec31fe7b
Bug 42714: Add missing use Koha::Script -cron to cron scripts
Add Koha::Script -cron to restrict_patrons_with_failed_notices.pl
and sftp_file.pl which used cronlogaction directly without importing
Koha::Script. Remove their now-redundant manual calls.

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: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:34 +00:00
Nick Clemens 996aefd280
Bug 42714: Remove redundant manual cronlogaction calls from scripts
Remove start/end cronlogaction calls and unused C4::Log imports
from scripts that use Koha::Script, as these are now handled
automatically by the INIT/END blocks.

Scripts with mid-execution informational logging (plugins_nightly,
harvest_oai, fines, process_message_queue) retain their C4::Log
import and non-start/end cronlogaction calls.

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: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:32 +00:00
Nick Clemens ecd056b21d
Bug 42714: Add automatic cronlogaction logging to Koha::Script
Add INIT and END blocks to Koha::Script that automatically call
cronlogaction on script start (with command line options) and
completion. This removes the need for scripts to manually handle
cron log entries.

This will mean that any script importing Koha::Script will be logged,
but I think the addition is beneficial.

To test:
1 - Ensure CronjobLog is enabled
2 - perl misc/cronjobs/fines.pl --test
3 - Confirm start and end are logged in action logs:
    http://localhost:8081/cgi-bin/koha/tools/viewlog.pl?src=&user=&modules=CRONJOBS&actions=&object=&info=&interfaces=&from=&to=&output=screen&basename=Export&report_name=&do_it=1
4 - Apply patch
5 - Repeat 2
6 - Reload the logs, confirm the new logs look the same
7 - Sign off

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: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:31 +00:00
Pedro Amorim 47a09c9182
Bug 16631: dbic changes
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:30 +00:00
Pedro Amorim c07c0f4a1f
Bug 16631: DBRev 26.06.008
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:29 +00:00
Pedro Amorim 92134fbec3
Bug 16631: (RM follow-up): Ensure syspref default on install
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:28 +00:00
Casey Conlin ba448064e2
Bug 16631: Bring back template. Fix test syntax.
RM amended: Add bug number to commit title

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:27 +00:00
Cameron E. Tidd 67a37a07c3
Bug 16631: Institute library limits
This patch implements the library_limit concept for Report/Reports. Staff with the correct permission and superlibrarians can set libaries to which the report should be visible, if the system preference is enabled.

Test plan:
Confirm Initial Settings
1. Log in to Koha staff with username: koha and password: koha and set your library as Centerville if it's not already.
2. Visit the Saved reports page, and create and save three reports: Report 1, Report 2, and Report 3. The SQL query for each report doesn't matter, it should just be something valid.
3. Visit the Patrons page and create two staff users: StaffReportLimitUser and StaffNoLimitPermission. Set their home libraries to Centerville and Fairfield, respectively.
4. Under Allow access to the reports module, ensure both users have report management permissions for Create, Delete, Execute.

Verify Patch Settings
5. Apply the patch and restart services. If using Koha Testing Docker (KTD): from the Koha repo directory, apply the patch (e.g. `git bz apply 16631`, or check out the patched branch with `git fetch && git checkout <branch>`), then run `ktd --shell` to enter the KTD shell and run `restart_all`. If the patched branch is already checked out, just enter the KTD shell and run `restart_all`.
6. Click Patrons in the top navbar to go to the Patrons page.
7. Search for "StaffReportLimitUser" in the patron search field.
8. Click StaffReportLimitUser to edit the patron record for this user.
9. Click More -> Set permissions to access the permissions screens for the StaffReportLimitUser.
10. In the row for Allow access to the reports module (reports), click Show details to view relevant permissions.
11. Confirm Manage Report Limits (manage_report_limits) is an option under Allow access to the reports module (reports).
12. Click the checkbox for Manage Report Limits to give this user this permission, and click Save.
13. Go to the System Preferences page (Click the More dropdown from the top nav bar, click Administration, click System Preferences).
14. Verify Reports is an option in the left side navigation bar, and that when clicked, you see the LimitReportsByBranch option.
15. Enable LimitReportsByBranch as described in step 14.
16. Go back to Saved reports as in Step 2.
17. Click Edit in the row menu for Report 1.
18. At the bottom of the Report 1 edit page, you should see a Library limitation select box.

Verify the Library Limitation Settings are Persisted
19. Choose Centerville from the select box and click Update SQL.
20. Reload the page, or navigate away from the page and go back to the edit screen for Report 1.
21. Verify that Centerville is still in the Library limitation select box.
22. Add Fairfield in the Library limitation select box and click Update and run SQL.
23. Go back to editing Report 1.
24. Confirm the Library limitation select box has both Centerville and Fairfield selected.
25. Duplicate Report 1.
26. Confirm the Library limitation select box has both Centerville and Fairfield selected.
27. Click Save Report.
28. Confirm the Library limitation select box has both Centerville and Fairfield selected.

Verify Reports are Limited Based on Library Limitations
29. Edit Report 1 to add Centerville in the Library limitation select box as the only choice and click Update SQL.
30. Edit Report 2 to add Fairfield in the Library limitation select box as the only choice and click Update SQL.
31. Report 3 should have no library limitations, so it should be visible to all libraries.
32. Go to Saved reports as in Step 2.
33. Confirm you do not see Report 2, and only Report 1 and Report 3.
34. In the upper right corner, click the drop down that says koha Centerville and click Set library to change your library.
35. Set your library to Fairfield.
36. Go to Saved reports as in Step 2.
37. Confirm you do not see Report 1, and only Report 2 and Report 3.

Verify Disabling LimitReportsByBranch Preference Shows All Reports
38. Disable LimitReportsByBranch preference as in steps 13 to 15.
39. Go to Saved reports as in Step 2.
40. Confirm Report 1, Report 2, Report 3 are visible.
41. Click to edit Report 1 and verify there is no Library limitation select box.

Verify Library limitations are Persisted When EnableFilteringReports is Toggled On or Off
42. Enable LimitReportsByBranch preference as in steps 13 to 15.
43. Ensure your library is set to Fairfield as in step 35.
44. Go to Saved reports as in Step 2.
45. Confirm Report 1 is not visible, and Report 2 and Report 3 are visible.
46. Click to edit Report 2 and verify the Library limitation select box is at the bottom of the page and that it has Fairfield selected.

Verify the Show all reports option shows all reports for users with the Manage Report Limits permission and superlibrarians
47. Go to Saved reports as in Step 2 and in the Show reports dropdown, select Show all reports, and click Apply filter.
48. Confirm the page reloads with all reports visible: Report 1, Report 2, and Report 3 should be visible.
49. Select Show my branch reports from the dropdown and click Apply filter.
50. Confirm Report 1 is not visible, and Report 2 and Report 3 are visible (you should only see reports for your branch, Fairfield).

Verify Report filters return the correct results
51. Go to Saved reports as in Step 2.
52. Verify you're viewing only your branch's reports in the filter.
53. Under Filter in the left sidebar, type "2" in the Keyword field and click Apply filter.
54. Confirm only Report 2 is visible.
55. Under Filter in the left sidebar, type "1" in the Keyword field and click Apply filter.
56. Confirm there are no reports visible, and the message "No saved reports match your criteria" is displayed.

Verify Library Limits column is available to superlibrarians and users with the Manage Report Limits permission
57. Go to Saved reports as in Step 2.
58. Click the Columns dropdown and ensure that the Library limits column is available to select for display. Select it.
59. Confirm that the Library limits column is now visible in the list of reports and that it shows the correct library limitations for each report (e.g., Report 1 should show 1 library limitation and Centerville (CPL) on hover, Report 2 should show 1 library limitation and Fairfield (FFL) on hover, and Report 3 should show no limitation).

Verify the Show all reports and Limit reports options are available to users that are not superlibrarian but have the Manage Report Limits permission
60. For the StaffReportLimitUser, add the permission for Manage Report Limits as described in steps 9 to 12. This will allow this user to see the Show all reports and Limit reports options.
61. Log out of the koha admin account. In the upper right corner, click the drop down that says koha Fairfield and click Log out.
62. Log in as StaffReportLimitUser, the previously created staff user with report permissions.
63. Confirm the staff user's library is Centerville in the upper right corner of the screen.
64. Go to Saved reports as in Step 2.
65. Confirm the filter option for Show reports is visible.
66. Click to edit Report 1.
67. Verify there is a Library limitation select box and that it can be interacted with.

Verify the Show all reports and Limit reports options are not available to users that are not superlibrarian and do not have the Manage Report Limits permission
68. Log out of the StaffReportLimitUser account.
69. Log in as StaffNoLimitPermission, the previously created staff user without the Manage Report Limits permission.
70. Confirm the staff user's library is Fairfield in the upper right corner of the screen.
71. Go to Saved reports as in Step 2.
72. Click the Columns dropdown and ensure that the Library limits column is not in the list of available columns to select for display.
73. Confirm the filter option for Show reports is not available.
74. Click to edit Report 2.
75. Verify there is no Library limitation select box at the bottom of the page.

[NEW] Verify library limits are auto-assigned and preserved correctly for non-permissioned users
76. Still logged in as StaffNoLimitPermission (Fairfield) from the previous section. Go to Saved reports and click New report. Enter "Limit Auto-Assign Test" as the report name and SELECT 1 as the SQL. Confirm there is no Library limitations select box visible on the form. Click Save.
77. Go back to Saved reports. Click Edit for Report 2 (limited to Fairfield). Add `-- test` as a trailing SQL comment and click Update SQL. This simulates a non-permissioned user saving an existing report that already has limits.
78. Go back to Saved reports. Click Edit for Report 3 (no library limits). Add `-- test` as a trailing SQL comment and click Update SQL. This simulates a non-permissioned user saving an existing report that has no limits.
79. Log out and log back in as StaffReportLimitUser (Centerville, has manage_report_limits).
80. Go to Saved reports. Use Show all reports if needed. Find "Limit Auto-Assign Test" and click Edit. Confirm the Library limitations select box shows Fairfield as the only selected library — the report was automatically limited to the creator's home library on create.
81. Find Report 2 and click Edit. Confirm the Library limitations select box still shows only Fairfield — the existing limit was preserved when the non-permissioned user saved the report.
82. Find Report 3 and click Edit. Confirm the Library limitations select box is empty — no limits were introduced when the non-permissioned user saved a report that had no existing limits.

Verify limited reports are not visible to users without permissions
83. While still logged in as StaffReportLimitUser from step 79, copy the URL for the edit page of Report 1.
84. Log out of the StaffReportLimitUser account.
85. Log in as StaffNoLimitPermission, the user without the Manage Report Limits permission.
86. Paste the copied URL for the edit page of Report 1 into the browser and navigate to it.
87. Confirm that you see an error message that says "This report is restricted and cannot be accessed by the current user." and that the page does not load the report details.

Run Unit Tests
88. Using Koha Testing Docker, in the Koha home directory run the command: ktd --shell to start the ktd shell.
89. Run command: reset_all to drop and recreate the koha database.
90. Run command prove t/db_dependent/Koha/Reports.t to run the test.
91. This file contains the unit tests for the reports_branches table and the new subtest `library limits enforcement on save and update`, which covers auto-assignment of branch limits on create and preservation of limits on update for non-permissioned users.
92. All tests should pass.
93. Run command prove t/Koha/Auth/Permissions.t
94. Permissions unit tests in this file were updated.
95. All tests should pass.
96. Run command prove t/db_dependent/Reports/Guided.t
97. The Guided report tests were updated to account for the new library limitations and permissions.
98. All tests should pass.

Signoff
99. Sign off if the steps above check out.

Sponsored-by: Ramapo Catskill Library System
Co-authored-by: Casey Conlin <casey.conlin@gmail.com>
Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:25 +00:00
Chris Nighswonger 60ee7fb527
Bug 41717: Bump PDF::Reuse and PDF::Reuse::Barcode versions
PDF::Reuse 0.43 fixes a TTFont/docProxy lifecycle bug that caused
prEnd() to crash when TrueType fonts were used.

PDF::Reuse::Barcode 0.09 is the current stable release.

Testing plan:

- Start ktd, note older perl modules versions under About Koha
- Apply patch
- ktd --shell
- sudo cpan -i PDF:Reuse; sudo cpan -i PDF:Reuse:Barcode
- Let cpan auto set itself up (not sure if sudo is needed above)
- `restart_all`
- Revisit About Koha; module versions should be 0.43 & 0.09 respectively

Signed-off-by: Eric Phetteplace <ephetteplace@cca.edu>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-28 14:32:22 +00:00
Pedro Amorim cfe7440dd7
Revert "Bug 34451: Add CSRF protection for REST API (cookie-auth only)"
This reverts commit 7716fac58d.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 16:06:36 +00:00
Andrii Nugged a445bb0ab3
Bug 42845: Add ILL config endpoint for Vue app
The ILL Vue app currently reads ILLModule and ILLPartnerCode through the system preferences service. That service requires parameters/manage_sysprefs, so a staff user with ILL permissions but without system preference administration permissions cannot load the ILL module.

Add an ILL-specific /ill/config REST endpoint protected by the ill permission, and switch the ILL Vue app to load its configuration from that endpoint. This follows the module-specific configuration endpoint pattern introduced by bug 33606 for the same manage_sysprefs problem in the ERM Vue app.

Security/compatibility considerations: the endpoint returns only allow-listed, non-secret ILL configuration values needed by the ILL Vue app. It does not expose the generic system preferences API, and /api/v1/sysprefs remains restricted to parameters/manage_sysprefs.

Regression test coverage: the new API test verifies that a staff user with the ill permission can read /ill/config, that a staff user without the ill permission cannot read it, and that the same ILL-only user still cannot access /api/v1/sysprefs.

Test plan:

1. prove -I. -It/lib t/db_dependent/api/v1/ill_config.t

2. prove -I. -It/lib t/db_dependent/api/v1/ill_users.t

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

Amended-by: Jonathan Druart
chmod +x t/db_dependent/api/v1/ill_config.t

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:48 +00:00
Emily Lamancusa 769e565890
Bug 38361: (QA follow-up) Set required variable for input validation
Built-in validation checks weren't functioning because the variable
required.sex was not getting set. Ensure that variable gets set, and
remove manual validation code that is no longer needed.

Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:47 +00:00
Bo Gustavsson 9d40cdee7f
Bug 38361: Gender field does not include a Required if the field is set to required in PatronSelfRegistrationBorrowerMandatoryField
Solves this bug and enhances the user experiance by replacing the old radio buttons with a less-confusing dropdown menu.

Test plan:

1. Open the setting "PatronSelfRegistrationBorrowerMandatoryField", find Gender(sex) and mark it as not required.
2. Go to the self registration form. No 'Required' label under gender.
3. Go back to the setting  "PatronSelfRegistrationBorrowerMandatoryField", find Gender(sex) and mark it as required.
4. Go to the self registration form. The 'Required' label is there.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:46 +00:00
Slava Shishkin ac31d8ae25
Bug 41227: Respect SuspendHoldsIntranet on patron holds page
Test plan:
1. Set SuspendHoldsIntranet to "Don't allow"
2. Place a hold for a patron
3. Open the patron holds table
4. Confirm the Suspend button is visible in the Suspend column
5. Apply the patch
6. Reload the patron holds table
7. Confirm the Suspend button is no longer visible
8. Set SuspendHoldsIntranet to "Allow"
9. Reload the patron holds table
10. Confirm the Suspend button is visible again

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:45 +00:00
Fridolin Somers ea185b6833
Bug 43098: Typo edit_formid in guided_reports_start.tt
Typo in edit SQL when authorised values category is wrong : 'op=edit_formid='

Test plan:

1) Create a new authorised values category WRONG
2) Create a new SQL report with : SELECT * FROM biblio WHERE title LIKE <<val|WRONG>>
3) Save the report
4) Delete authorised values category WRONG
5) Run the SQL report
6) You see message 'The authorized value category (WRONG) you selected does not exist.'
7) Click on 'Edit SQL'
=> You comme back to editing the correct SQL report

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:44 +00:00
Lari Taskula d0b458af58
Bug 42298: Automatic renewals should only respect the "No automatic renewals before" rule
To test:
1. prove t/db_dependent/Circulation.t
2. Observe success

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:43 +00:00
Lari Taskula 54ac795b76
Bug 42298: Add a failing test
To test:
1. prove t/db_dependent/Circulation.t
2. Observe the following failures

t/db_dependent/Circulation.t .. 17/87
    #   Failed test 'Bug 42298: No autorenew before rule is respected'
    #   at t/db_dependent/Circulation.t line 1134.
    #          got: '0'
    #     expected: '1'

    #   Failed test 'Bug 42298: No autorenew before rule is respected'
    #   at t/db_dependent/Circulation.t line 1135.
    #          got: 'too_soon'
    #     expected: 'auto_renew'

    #   Failed test 'soonest_renew_date is not returned because this issue can be renewed'
    #   at t/db_dependent/Circulation.t line 1136.
    #          got: '2026-04-21T23:59:00'
    #     expected: undef

    #   Failed test 'No renewal before is 99, patron opted out of auto_renewal so can renew'
    #   at t/db_dependent/Circulation.t line 1141.
    #          got: '0'
    #     expected: '1'
    # Looks like you failed 4 tests of 124.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:42 +00:00
Jonathan Druart 34da25f751
Bug 42632: Fix "Manage bundle" after filters are shown/hidden
If "Show/Hide filters" link is clicked, the "Manage bundle" buttons do not longer work.

Test plan:
Edit a record, set leader 7=collection
In the holding list click "Manage bundle"
Click "Show filters"
Click one of the "Manage bundle" button
=> Without this patch nothing happens
=> With this patch applied the table displays the nested table with the
bundle details

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Anni <anni.maki-mantila@turku.fi>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:41 +00:00
Pedro Amorim d7ff63f20b
Bug 41898: DBRev 26.06.00.007
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:39 +00:00
Paul Derscheid 1f45a43f1d
Bug 41898: (QA follow-up) Skip clash detection when collecting a booking
Transitioning a booking to 'issued' no longer skipped clash detection,
and Item::check_booking treats any live checkout on the item as a
clash. Switching an on-site checkout of a booked item to a regular
checkout (SwitchOnSiteCheckouts) therefore crashed AddIssue with an
unhandled Koha::Exceptions::Booking::Clash; collection could fail the
same way whenever the biblio's bookable item pool had shrunk since the
booking was placed.

- short-circuit no-op stores before validation; circulation issues
  redundant status writes that must not re-trigger the checks
- rename _is_final_status_transition to _skip_clash_detection and also
  skip when transitioning to 'issued' from an active status, as long
  as the booked period and item are unchanged
- keep validating period/item changes and reactivations of terminal
  bookings
- cover the on-site switch in Circulation.t and the store() semantics
  in Booking.t

Test plan:
1. Enable OnSiteCheckouts and SwitchOnSiteCheckouts
2. Place a booking starting today for a patron
3. Check the booked item out to that patron from Circulation > Check
   out with 'On-site checkout' ticked. Do NOT use the bookings page's
   'Transform to checkout' button: it submits a fixed field set that
   does not carry the on-site flag (Bug 36789), so the checkout would
   silently be a regular one and step 4 could not reproduce the switch
4. The booking shows 'Issued'. Scan the same barcode for the same
   patron again to switch the on-site checkout to a regular one; this
   crashed with Koha::Exceptions::Booking::Clash before the patch and
   now succeeds with the booking still 'Issued'
5. prove t/db_dependent/Koha/Booking.t t/db_dependent/Circulation.t

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:38 +00:00
Paul Derscheid 15dfbc2f08
Bug 41898: (QA follow-up) Only list uncollected bookings on the pending list
The pending bookings page lists items that have not been collected yet,
but the status filter added when the server-side active pre-filter was
removed included 'issued' bookings, so bookings reappeared on the list
after the item had been checked out.

- restrict the status filter to 'new'

Test plan:
1. Place a booking starting today and open Circulation > Bookings to
   collect; the booking is listed
2. Check the item out to the booking patron; reload the list and
   confirm the booking is gone

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:37 +00:00
Martin Renvoize 91556ada91
Bug 41898: Load additional-filters.js on the circulation page
The rewrite of js/tables/bookings.js in this patchset builds on
AdditionalFilters, which is provided by js/additional-filters.js. The
patron details page loads that asset but the circulation page did not,
so the bookings tab on circ/circulation.pl died with 'AdditionalFilters
is not defined' and the table never initialised.

Test plan:
1) Check out to a patron with bookings and open the Bookings tab on
   the checkout page; confirm the table renders and no JS error is
   logged in the console

Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:36 +00:00
Martin Renvoize 40b9c8e29c
Bug 41898: Update tests for new booking status lifecycle
- Bookings.t: rewrite filter_by_active to verify status-based filtering;
  new/issued are active, cancelled/completed are not; past-window 'new'
  bookings (uncollected) remain active
- Booking.t: checkout relation uses 'issued'; old_checkout uses 'completed';
  clash detection covers issued transition; notice test updated
- Circulation.t: AddIssue sets 'issued'; AddReturn sets 'completed'; plan
  expanded to cover the full checkout → return lifecycle

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:35 +00:00
Martin Renvoize bf8237ae54
Bug 41898: Fix callers of bookings API after removing server-side active filter
The bookings REST controller previously applied filter_by_active (status IN
'new','issued') as a server-side pre-filter. Removing that pre-filter means
callers that relied on it must now supply an explicit status filter.

- pendingbookings.tt: add status filter to additional_filters so the
  pending-bookings-to-collect page still shows only new+issued bookings
- place_booking.js: correct status values from the obsolete 'pending','active'
  (never valid in the schema) to the correct 'new','issued'

Test plan:
1. Navigate to Circulation > Bookings to collect.
   - Confirm only bookings with status "New" or "Issued" are listed.
   - Complete a booking (check out then return the item) and confirm it
     disappears from the list.
2. On any biblio with multiple bookings, open the Place booking modal.
   - Create overlapping bookings where one is completed or cancelled.
   - Confirm the modal does not treat completed/cancelled bookings as active
     conflicts when checking availability.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:34 +00:00
Martin Renvoize c7673a9d6a
Bug 41898: Introduce 'issued' status and update booking display/filtering
Implements a 4-state booking lifecycle: new → issued (on checkout) →
completed (on return), plus cancelled at any point.

Backend changes:
- AddIssue sets booking status to 'issued' (was incorrectly 'completed')
- AddReturn transitions 'issued' → 'completed' via booking_id on the issue
- filter_by_active updated to status-based: status IN ('new', 'issued');
  previously it filtered by date range — active/inactive now reflect booking
  lifecycle rather than the current date relative to start/end dates
- _is_final_status_transition reverted to only skip clash detection for
  terminal statuses 'cancelled' and 'completed'; 'issued' is active and does
  not bypass clash detection
- GET /api/v1/bookings no longer pre-filters to active bookings server-side;
  callers must supply an explicit status filter if they need active-only

Display changes:
- Patron bookings tab: replaces the date-based expired/active toggle with
  per-status filters (completed, cancelled) using the AdditionalFilters
  framework; active bookings (new/issued) are shown by default
- Biblio bookings list: same status-based filters; removes the date-based
  expired filter that was still present
- Status column is always visible on both views (was previously hidden on
  the patron tab unless expired bookings were shown)
- Status badges: new=bg-success, issued=bg-info, completed/cancelled=bg-secondary
- Timeline isActive check corrected to ['new','issued']
- patron-detail-tabs.inc: rename expired_bookings_count → closed_bookings_count
  to reflect status-based (not date-based) counting
- Filter button colour fixed to use $link-color so both views are consistent

Test plan:
1. Enable the EnableBookings system preference.
2. Create a biblio with at least one bookable item.
3. Place a booking for a patron via the biblio's Bookings tab.
   - The booking should appear with status badge "New" (green).
4. Check out the booked item to that patron (Circulation → Check out).
   - Reload the Bookings tab; the booking should now show "Issued" (blue).
5. Return the item (Circulation → Check in).
   - Reload the Bookings tab; the booking should now show "Completed" (grey).
6. Place a second booking and cancel it from the Bookings tab Actions menu.
   - The booking should show "Cancelled" (grey), with the reason if provided.
7. On the biblio Bookings tab:
   - Completed and cancelled bookings are hidden by default.
   - Clicking "Include completed" shows completed bookings.
   - Clicking "Include cancelled" shows cancelled bookings.
   - Both can be toggled independently.
   - The Status column is always visible.
8. Navigate to the patron's detail page → Bookings tab.
   - Active bookings (new/issued) are shown by default.
   - Completed and cancelled can be toggled independently using the same
     filter buttons.
   - The Status column is always visible.
9. Confirm via the REST API that GET /api/v1/bookings (no filters) returns
   bookings of all statuses, not just active ones.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:33 +00:00
Martin Renvoize fde2bc5680
Bug 41898: Automated Schema Update
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:32 +00:00
Martin Renvoize 4633d37931
Bug 41898: Add atomicupdate and update schema for 'issued' booking status
Extends bookings.status enum to ('new', 'cancelled', 'issued', 'completed').

Migrates existing data intelligently: rows with an active checkout in the
issues table (via booking_id) are updated to 'issued'; rows whose item has
already been returned retain 'completed', which is already correct under the
new scheme where 'completed' means the booking was fully fulfilled and the
item returned.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:31 +00:00
Brendan Lawlor 5f99bbb772
Bug 41879: (QA follow-up) Add pod for new sub _pickup_valid_for_item
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:30 +00:00
Lucas Gass ada88e3a83
Bug 41879: (follow-up) Add a test
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:29 +00:00
Lucas Gass 7d1521b943
Bug 41879: (follow-up) Bail with error code when no items on target bib
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:28 +00:00
Lucas Gass 9ca6d01f22
Bug 41879: (follow-up) Add helper function to avoid repeating code
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:27 +00:00
Lucas Gass 7f331590fa
Bug 41879: update move_hold unit tests
Signed-off-by: Eric Phetteplace <phette23@gmail.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:26 +00:00
Lucas Gass 47dd1f1844
Bug 41879: Disallow moves to invalid pickup locations for next available holds
Signed-off-by: Eric Phetteplace <phette23@gmail.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:25 +00:00
Lucas Gass 560efd018b
Bug 41879: Disallow moves to invalid pickup locations for items
To test:

1. Set ReservesControlBranch is set to 'item's home library'
2. Set the deafult 'Holds and bookings policies by item type' for the itype BK to allow holds from patrons from any library, but pickup at CPL only
3. Have a bib owned by another library ( I used MPL ), and bib owned by CPL. Both are the item type BK, and each has one item on the bib.
4. Place a hold for a patron on bib A for pickup at MPL
5. Move the hold to bib B
6. Note that the hold moves successfully, and is still set for pickup at MPL
7. Login and try to trigger the hold, nothing happens
8. APPLY PATCH, restart_all
9. Try spets 3 - 6 again. This time on step 6 you should see a message that the hold cannot be moved.

Signed-off-by: Eric Phetteplace <phette23@gmail.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:24 +00:00
Jonathan Druart 4c3e4b8bd7
Bug 42396: Adjust 'use' statements
Always include the plural version, not the singular one (the plural
imports the singular)

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:23 +00:00
Olivia Reynolds 0670379b76
Bug 42396: Remove C4::Stats::UpdateStats and delete module
This patch replaces all calls to C4::Stats::UpdateStats with the wrapped
function from the Koha namespace. As this is the last function in C4::Stats, the
module is also deleted.

Several tests depended on transitive includes to Koha::Statistics through
C4::Stats. I've identified uses of that module by searching, and added the
necessary use in each case. I've run `prove -r t xt` with this patch applied and
didn't hit any relevant errors.

Test plan:
1. Inspect the patch, and verify each changed call maintains the same parameters.
2. Apply patch.
3. Prove the following files:
   - t/db_dependent/Accounts.t
   - t/db_dependent/Circulation.t
   - t/db_dependent/Koha/Account.t
   - t/db_dependent/Koha/ILL/Request.t
   - t/db_dependent/Koha/Item.t
   - t/db_dependent/Koha/Pseudonymization.t
   - t/db_dependent/Koha/Recalls.t

Sponsored-by: Open Fifth

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:22 +00:00
Emily Lamancusa 9cad4c5c8d
Bug 20573: Make NotifyBorrowerDeparture's date math consistent with MembershipExpiryDaysNotice
To test:
1. Set both system preferences NotifyBorrowerDeparture and
   MembershipExpiryDaysNotice to 10 days
2. Create or edit a patron account with the following settings:
    - expiry date is exactly 10 days in the future
    - add an email address on the patron account
    - set messaging preferences to receive email notices for Patron
      account expiry
3. Run the membership expiry cron job to generate a notice:
   ktd --shell
   perl misc/cronjobs/membership_expiry.pl -c
4. Check notices tab on patron account
--> A membership expiry notice has been queued for the patron
5. Click back to the patron details
--> Note that there is no alert that the patron account will be expiring
    soon
6. Apply patch and restart_all
7. Refresh the patron details page
--> You should now see an alert that the account will be expiring soon

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 15:53:18 +00:00
Emmi Takkinen fdf37a2630
Bug 42699: Display Koha error message when attempting to save non-unique barcode in Edit serials
When you edit an already received serial from Serial collection page
using Edit serials and edit items buttons, and try to save a barcode
that already exists on another item, saving leads to Error 500.
There is no barcode validation for editing serials, this patch adds it.

To test:
1. Go to serials module.
2. Search for a serial that is not expired to edit and click on the title.
3. Go to Serial collection from the menu.
4. Use the checkboxes to mark an already received (eg. usually older,
arrived, not expected) serial and click Edit serials.
5. Click on Edit item.
6. Replace the barcode with a unique barcode that is already in use
and hit save.
=> Error 500 is raised.
7. Apply this patch and restart services.
8. Attempt to save serial with already used barcode again.
=> A standard Koha error message is displayed stating that barcode
must be unique.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:13:09 +00:00
Martin Renvoize 7355dade3b
Bug 43071: Isolate holds_per_day-sensitive tests from ambient circulation rules
t/db_dependent/Holds.t, t/db_dependent/ILSDI_Services.t, and
t/db_dependent/api/v1/holds.t all fail if a site has a global
'Holds Allowed (Daily)' circulation rule configured, because
C4::Reserves::CanItemBeReserved checks holds_per_day before
holds_per_record/reservesallowed, so an ambient daily-cap rule
returns tooManyReservesToday and masks the status these tests
actually exercise (tooManyReserves, noReservesAllowed, OK, or the
API's hold_limit override). The affected subtests never set
holds_per_day themselves, so they unintentionally depend on the
database having no such rule configured.

Test plan:
1. On a fresh db, set a global 'Holds Allowed (Daily)' circulation
   rule (Administration > Circulation and fine rules) to 1.
2. prove t/db_dependent/Holds.t t/db_dependent/ILSDI_Services.t
   t/db_dependent/api/v1/holds.t
   => fails before this patch, passes after.
3. Remove the circulation rule and re-run the same tests.
   => still passes (no regression for the default/unconfigured case).

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:13:08 +00:00
Paul Derscheid fd3addf35f
Bug 38358: (QA follow-up) Return opened panel from dateChanged
Builds on the panel-detection logic by having dateChanged report which
panel it opens, so onChange can adjust that panel's "To date" picker
directly without waiting on the slide animations to settle.

- dateChanged returns "showHoliday" when editing, "newHoliday" when adding
- onChange maps that to datecancelrange/dateofrange, in place of the
  setTimeout and the :visible check

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:13:07 +00:00
Emmi Takkinen aaa3954c8f
Bug 38358: Do not allow selecting past dates when editing existing holidays "To date" input
When you are editing holidays "To date" inputs calendar doesn't follow the
"From date" date. Instead calendar offers you today's date/month and
you must click calendar forward to the right date/month. This patch
adds check to determine if user is adding new holiday or editing
existing one and adjusts the code to disable past dates counting from
selected "From date" in both cases.

To test:
1. Navigate to calendar tool (Tools->Calendar).
2. Add new holiday and select "To date" for it.
=> Note that you are unable to select dates past from "From date".
3. Edit existing holiday and select "To date" for it.
=> Note that "To date" input calendar allows selecting dates past
"From date".
=> If you chosed holiday from incoming month, note that "To date"
calendar still opens up to this month.
4. Apply this patch, restart services if needed.
5. Edit existing holiday again.
=> Note that now "To date" inputs calendar works the same way as
it is working when your adding a new holiday.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:13:06 +00:00
Kyle M Hall 430281a59a
Bug 43101: search_for_data_inconsistencies.pl inlines every biblionumber in the catalog as an OR chain
Queries that use OR instead of IN are less efficient and can lead to
situations where the query takes a very long time to complete due to
row level scanning. We found one instance where they query had been
running for over 10 days!

This patch makes ids() return a subquery instead of a list of results,
and switches the callers to using IN. The database now resolves the set
itself and the queries stay a constant size no matter how large the
catalog is. This is super efficient because the subquery doesn't even
execute as a separate query since biblionumber is the primary key!

Test Plan:
1) Apply the unit tests patch
2) prove t/db_dependent/Koha/Database/DataInconsistency.t
3) Note the ids() subtest fails
4) Apply this patch
5) prove t/db_dependent/Koha/Database/DataInconsistency.t
6) Note all tests pass!
7) Run misc/maintenance/search_for_data_inconsistencies.pl
8) Note the output is the same as before the patch
9) Bonus: Re-run it with DBIC_TRACE=1 and note the queries now use
   IN (SELECT ...) instead of one OR clause per bib!

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:13:05 +00:00
Kyle M Hall 94695b74a1
Bug 43101: Add unit tests
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:13:04 +00:00
Matt Blenkinsop a5d12147f6
Bug 43121: Convert all groups to use explicit labels
Test plan:
1) Install another language
2) Navigate to a vendor record
3) The group names should translate correctly and display in the split screen view

Signed-off-by: Michaela <michaela.sieber@kit.edu>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:13:03 +00:00
Matt Blenkinsop 53e9d026ce
Bug 43121: Use an explicit label for group and tab names rather than a translated string
Signed-off-by: Michaela <michaela.sieber@kit.edu>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:13:02 +00:00
Saiful Amin b98a0c87fb
Bug 42861: System warning about missing encryption_key on a fresh instance
This patch updates the koha-create command to generate the random encryption key
using pwgen with the number of characters set to 32.

Test plan:
1. In ktd, create a new instance:
    - sudo koha-create --create-db test1
    - Check the /etc/koha/sites/test1/koha-conf.xml file and verify that the encryption_key
    is having the placeholder text '__ENCRYPTION_KEY__'

2. Apply the patch and copy the new koha-create command
    - git bz apply 42861
    - sudo cp /kohadevbox/koha/debian/scripts/koha-create /usr/sbin/koha-create

3. In ktd, create a new instance:
    - sudo koha-create --create-db test2
    - Check the /etc/koha/sites/test2/koha-conf.xml file and verify that the encryption_key
    is generated and has 32 characters random string.

4. Sign-off

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:13:01 +00:00
Matt Blenkinsop 7b199653ed
Bug 43119: Restore item and hold links to the log viewer
INSERT INTO action_logs (timestamp, user, module, action, object, info, interface) VALUES
    (NOW(), 5, 'CIRCULATION', 'ISSUE', 5,
     '{"issue":1,"branchcode":"CPL","itemnumber":1,"confirmations":["TOO_MANY"],"forced":["DEBT"]}', 'intranet'),
    (NOW(), 5, 'CIRCULATION', 'RETURN', 5,
     '1', 'intranet'),
    (NOW(), 5, 'HOLDS', 'CREATE', 7,
     '{"hold":7,"biblionumber":1,"itemnumber":1,"confirmations":["HIGHHOLDS"],"forced":["HOLD_POLICY_OVERRIDE"]}', 'intranet');

Test plan:
1) Without the patch applied, run the populate SQL above against the kohadev database.
2) Go to Tools > Log viewer
3) Under Modules, select only Circulation - leave the date range empty and click Submit.
4) Observe the ISSUE row's Info column shows the raw JSON payload as plain text, and the RETURN row shows the bare itemnumber "1" as plain text - neither is a clickable link to the item.
5) Go back, select only the Holds module, click Submit, and observe the CREATE row's Info column likewise shows raw JSON with no hold/item links.
6) Apply the patch.
7) Run yarn build && restart_all, then hard refresh the browser.
8) Repeat step 3.
9) Confirm the ISSUE row's Info column now shows a link reading "Item <barcode> CPL" (e.g. Item 3999900000001 CPL) - the branchcode comes from the JSON payload - and clicking it opens the item page. Override information is also displayed correctly
10) Confirm the RETURN row's Info column now shows a link reading "Item <barcode>" (no branchcode) opening the same item's detail page - verifying the bare-itemnumber payload path.
11) Repeat step 5. Confirm the CREATE row now shows "Hold 7 on biblio 1" linking to reserve/request.pl?biblionumber=1, followed by " - Item 1" linking to the item detail page. Override information is also displayed correctly

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:13:00 +00:00
Samuel Sowanick 1254997e69
Bug 42928: Update log viewer action terminology - Return to Check-in
The log viewer results table uses "Return" for "Check-in" actions.

See the terminology list:
https://wiki.koha-community.org/wiki/Terminology#C

Test plan:
1. Check out an item to a patron, then check it back in again.
2. Go to the log view (Tools > Additional tools > Log viewer).
3. Click "Submit".
4. Note that the action column for the results shows "Return".
5. Apply the patch.
6. Refresh the log viewer page.
6. Note that the action column now correctly shows "Check-in".

Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:59 +00:00
Tomás Cohen Arazi 7716fac58d
Bug 34451: Add CSRF protection for REST API (cookie-auth only)
This patch adds Koha::REST::Plugin::CSRF which enforces CSRF token
validation on state-changing requests (POST/PUT/DELETE/PATCH) when
a CGISESSID cookie is present.

Requests using OAuth2 or HTTP Basic Auth are not affected since they
don't use session cookies and are inherently immune to CSRF.

The client-side already sends the CSRF-TOKEN header via http-client.js
(added in Bug 34478). This patch adds the server-side enforcement.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Koha/REST/Plugin/CSRF.t
=> SUCCESS: Tests pass!
3. Run:
   k$ prove t/db_dependent/api/v1/csrf.t
=> SUCCESS: Tests pass!
4. Tests cover:
   - GET/HEAD/OPTIONS requests bypass CSRF check
   - POST/PUT/DELETE without CGISESSID bypass CSRF check
   - POST with CGISESSID but no CSRF-TOKEN returns 403
   - POST with CGISESSID and invalid CSRF-TOKEN returns 403
   - POST with CGISESSID and valid CSRF-TOKEN passes through
   - PUT and DELETE enforcement
5. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:58 +00:00
Tomás Cohen Arazi 058c476bc7
Bug 42732: Migrate catalogue/detail.tt addToBundle $.post to api-client.js
Replace direct $.post() to /api/v1/items/{id}/bundled_items with
ItemAPIClient.bundled_items.add(), which uses http-client.js and
automatically includes the CSRF token header.

The caller passes return_response: true because it needs access to
the raw response status code and body for fine-grained error handling
(different user-facing messages for 409/already_bundled, 409/checked_out,
409/reserved, 404/not_found, 400/failed_nesting). http-client.js
default behavior throws a plain Error on non-2xx responses, losing
the status code and error_code fields the UI needs.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:57 +00:00
Tomás Cohen Arazi e2b0883811
Bug 42731: Migrate modal-claims.inc $.post to api-client.js
Replace direct $.post('/api/v1/return_claims', ...) with the new
ClaimAPIClient, which uses http-client.js and automatically includes
the CSRF token header.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:56 +00:00
Tomás Cohen Arazi f5a1f41d35
Bug 42730: Migrate place_booking.js $.post to api-client.js
Replace direct $.post() and $.ajax() calls to /api/v1/bookings with
the new BookingAPIClient, which uses http-client.js and automatically
includes the CSRF token header.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:55 +00:00
Pedro Amorim f5e8cdb399
Bug 43139: Sort by ID DESC instead of ID ASC
Test plan:
1) Enable ILLModule
2) Ensure at least 2 ILL requests exist
3) Go to <staff_url>/cgi-bin/koha/ill/home
4) Confirm the most recent ILL request (highest ID) shows first

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:54 +00:00
Aleisha Amohia 33f4336974
Bug 33364: Prevent Holds to Pull from including items with pending recalls
This patch checks for waiting or in_transit items allocated to recalls, and excludes them from the Holds to Pull report.

To test:

1. Enable UseRecalls and set the recalls-related circulation rules
2. Check out an item to Patron A
3. Place an item-level hold on the item for Patron B
4. Log into the OPAC as Patron C and search for the item. Place an item-level recall on the item.
5. Go back to the staff interface and check in the item. This should prompt a pop-up to confirm the recall as waiting. Confirm the recall as waiting.
6. Go to Circulation -> Holds to pull.
7. Notice that even though the item is allocated for a recall, the hold is showing. It should not be showing in this page.

8. Apply the patch, restart services and refresh the page

9. The item allocated to a recall should no longer be showing in the Holds to pull report

10. Go to the record detail page and go to the Recalls tab
11. Revert the recall from its waiting status
12. Go back to the Holds to pull page. The record will now show for the hold.
13. Go to the Circulation -> Check in page
14. Go to the menu in the top-right of the page and click Set library
15. Change your library to a different one
16. Check in the item to confirm the recall as waiting and trigger a transfer
17. Go back to the Holds to pull page
19. The item should no longer show on the Holds to pull report

20. Confirm tests pass t/db_dependent/Koha/Holds.t

Sponsored-by: Catalyst IT
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:53 +00:00
Jonathan Druart f2cca7c630
Bug 43110: (bug 28308 follow-up) Disable checkboxes on the detail view
Checkboxes must be disabled on the detail page as we cannot submit the
changes.

Test plan:
1 - Edit a patron, set a value for advance notices, do not check any transports
2 - View the details for the patron:
    http://localhost:8081/cgi-bin/koha/members/moremember.pl?borrowernumber=5
3 - Note the checkboxes are disabled

QA: The logic is a bit weird as messaging_form_inactive is not set if the
pref is off, but the table is not displayed in this case.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:52 +00:00
Lucas Gass 3fac313a53
Bug 42930: Replace title with borrowertitle
To test:
1. Have some overdues
2. Go to circ/overdue.pl
3. Notice the the 'Patron' column the name is prefixed with the biblio title
4. This is meant to be the borrowertitle
5. APPLY PATCH, restart_all
6. Now you should see only the borrower/parton name and info.
7. Edit the patron and add a " Salutation"
8. In the overdue report this should now display

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:51 +00:00
Lucas Gass 35bbb013ba
Bug 42166: Check for 008 before adding the date to it
To test:
1. Edit or create a framework that does not require the 008.
2. Make or edit a record in that framework without a 008.
3. Turn on EnableAdvancedCatalogingEditor, and make sure you are using the advanced editor.
4. Go to the record from step 2 and click "Edit as new (duplicate)"
5. The record won't load in the advanced editor.
6. APPLY PATCH
7. Try again, this time the record should load when using "Edit as new (duplicate)".

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:50 +00:00
Jonathan Druart 96de51c53d
Bug 42981: Prevent toLowerCase to fail when comparing int and string
If stateSave is turned on and one of the option has a value with _id set
to an Integer, toLowerCase is not available and a JS error is raised:
 Uncaught (in promise) TypeError: this._id.toLowerCase is not a function

Test plan:
1.) Admin - table settings: cgi-bin/koha/admin/columns_settings.pl#eholdings|packages
Activate checkbox "Save search state on page change"

2.) Go to ERM : cgi-bin/koha/erm/eholdings/local/packages
And create two packages:
 - One package with My Vendor assigned.
 - One package without a vendor.

3.) Filter the package results list by My Vendor. --> List is filtered to one package

4.) Leave the page, for example by navigating to Agreements.
Return to the Packages list and you will see the issue  (See also attached Screenshot)

=> Without the patch
The package results list becomes unresponsive / hangs after returning to the page.

=> With the patch
The package results list should load and display the filtered results correctly.

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:49 +00:00
Pedro Amorim 959ba68ad0
Bug 42489: DBRev 26.06.00.005
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:48 +00:00
Tomás Cohen Arazi 3edd575263
Bug 42489: Pass unallocated mode to real-time holds queue
When RealTimeHoldsQueueUnallocated is enabled, the background job
passes unallocated => 1 and delete => 0 to update_queue_for_biblio.
This skips deleting existing queue entries and only processes holds
not already allocated, matching the --unallocated behavior of
build_holds_queue.pl.

Test plan:
1. Apply patches
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/BackgroundJob/BatchUpdateBiblioHoldsQueue.t
=> SUCCESS: Tests pass!
3. Tests verify:
   - delete => 1 and no unallocated when pref is off
   - delete => 0 and unallocated => 1 when pref is on
4. Sign off :-D

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:47 +00:00
Tomás Cohen Arazi 5345fbf908
Bug 42489: Add RealTimeHoldsQueueUnallocated syspref
Adds a new system preference to control whether the real-time
holds queue processes only unallocated holds (faster, additive)
or rebuilds the entire queue for the affected biblio (current
behavior, more thorough).

Default is off (preserving current behavior).

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
  k$ perl installer/data/mysql/updatedatabase.pl
=> SUCCESS: New preference added
3. Verify RealTimeHoldsQueueUnallocated appears in
   Administration > System preferences > Circulation
4. Sign off :-D

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:46 +00:00
Pedro Amorim 2431af105d
Bug 40445: DBRev 26.06.00.005
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:45 +00:00
Martin Renvoize 09642be4db
Bug 40445: (follow-up) Add default cud- op value to cashup selected form
The two-phase cashup workflow added a 'Cashup selected registers' modal
whose form's op hidden input had an empty value, populated at runtime by
JavaScript. xt/find-missing-op-in-forms.t parses templates statically and
flagged the empty op value as not starting with 'cud-' (see bug 34478).

Set a sensible default of 'cud-cashup_start'; the JS click handlers still
overwrite the value before submission, so behaviour is unchanged.

Test plan:
1. prove xt/find-missing-op-in-forms.t => now passes

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:44 +00:00
Martin Renvoize a4ba11af06
Bug 40445: (follow-up) Fix TT in Tag issue
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:43 +00:00
Pedro Amorim f1766a206b
Bug 40445: (RM follow-up): Fix POD copy paste
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:42 +00:00
Martin Renvoize 53d498e145
Bug 40445: (QA follow-up) Show cashup summary modal when completing from registers.pl
When a single-register cashup is completed from the Cash summary page
(registers.pl), pos/register.pl redirected back to registers.pl without
the '#cashup-<id>' hash, so the hash-watcher in registers.tt never
triggered the cashup summary modal. Completing the same cashup from the
Transaction history page (register.pl) worked because that branch
already appended the hash.

Append '#cashup-<id>' to the registers.pl redirect URL too, so both the
'Record cashup' (quick) and 'Complete cashup' (two-phase) actions on the
Cash summary page open the summary modal on return.

Test plan:
1. Have at least one register with cash transactions.
2. From Cash summary (registers.pl), click 'Record cashup' on a register
   with no cashup in progress; complete it.
   => On return, cashup summary modal opens automatically.
3. Start a two-phase cashup; from Cash summary click 'Complete cashup'
   and confirm.
   => On return, cashup summary modal opens automatically.
4. From Transaction history (register.pl), complete a cashup as before.
   => Modal still opens (regression check).

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:41 +00:00
Martin Renvoize c51f4b7cca
Bug 40445: (QA follow-up) Add missing [% USE raw %] in cashup_summary.inc
The cashup summary modal include uses the \$raw filter on
Koha.CSPNonce, but never declared [% USE raw %]. The QA template
tester (which processes templates through a fresh Template::Toolkit
instance) reports "valid_template: filter not found" because the
\$raw plugin is not loaded.

Add [% USE raw %] at the top of the include, matching the convention
used by other includes that emit nonce attributes (e.g.
includes/str/cataloging_additem.inc).

Test plan:
1. Run qa-test-tools against the bug branch -- cashup_summary.inc
   passes valid_template.
2. Open the cashup summary modal -- the <style> tag still receives
   a CSP nonce and the embedded styles render correctly.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:40 +00:00
Martin Renvoize 7798332412
Bug 40445: (follow-up) Show actual cash removed alongside expected on discrepancy
The cashup summary modal labelled the per-payment-type cash row "Cash
removed from register", but the value came from the session
transactions (with CASHUP_SURPLUS/DEFICIT lines excluded), so it
represented the *expected* cash, not the amount actually counted at
cashup. When a deficit or surplus existed, the reconciliation row
appeared as the difference between two figures the user could not
both see.

Render two rows when surplus_total or deficit_total is set on a
completed cashup: "Expected cash total" (from total_grouped) and
"Cash removed from register" (from cash_register_actions.amount,
already exposed as data.amount). The reconciliation row below now
visibly equals the difference. Balanced cashups and the in-progress
preview keep the original single-row display, and the negative-cashup
branch flips the labels to "Expected cash to add to register" /
"Cash added to register".

Test plan:
1. Configure a cash register and record a few cash payments.
2. Trigger a cashup, enter the exact expected amount; open the
   summary modal -- a single "Cash removed from register" row is
   shown matching the expected total.
3. Repeat with an amount lower than expected -- "Expected cash
   total" and "Cash removed from register" both appear, and the
   "Cashup deficit" row is the difference.
4. Repeat with an amount higher than expected -- the actual row
   exceeds the expected row and "Cashup surplus" matches the gap.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:39 +00:00
Martin Renvoize 86d034e628
Bug 40445: (follow-up) Clarify cashup summary cash row wording and sign
A cashup is the process of removing cash from the till to send it
to the bank, so labelling the headline cash line "Cash added to
register" was confusing. Re-word it to "Cash removed from register"
in the typical case and "Cash added to register" in the edge case
where the cashier has had to top up the till to cover refunds.

While doing this, align the sign convention of total_grouped with
that of the overall total field on the summary: positive means cash
collected (i.e. removed from the register at cashup time), negative
means cash had to be added back into the register. Previously
total_grouped used the raw accounting sign (credits negative, debits
positive), so a typical $2 cash session would display "Total: 2.00"
and "Cash added to register: -2.00", which was internally
inconsistent. The conditions on the cash and other-payment-type rows
in cashup_modal.js are swapped to match the new convention.

Test plan:
1. Apply the patch and rebuild JS (yarn js:build) inside KTD.
2. As a staff user with permission to manage cash registers, take
   payments via several payment types (some cash, some not).
3. Trigger a cashup and view the cashup summary modal.
4. Confirm the "Total" line and the per-payment-type lines all
   display as positive amounts in the typical "money was collected"
   scenario, with the cash row labelled "Cash removed from
   register".
5. Issue some refunds large enough to exceed cash collected, then
   cash up again.
6. Confirm the cash row is labelled "Cash added to register" with a
   negative amount and the per-non-cash-type rows are labelled
   "{type} to add" where applicable.
7. Run prove t/db_dependent/Koha/Cash/Register/Cashup.t and prove
   t/db_dependent/Koha/Cash/Register.t inside KTD; both should
   pass.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:38 +00:00
Martin Renvoize 463b92df52
Bug 40445: (QA follow-up) Add nonce, POD, and test isolation
This patch resolves three QA findings on the cashup reconciliation
work:

* Add a CSP nonce to the inline <style> block in cashup_summary.inc
  so it is not flagged by the forbidden_patterns check.
* Document the private _get_session_end method on
  Koha::Cash::Register::Cashup so pod_coverage passes.
* Mock CashupReconciliationNoteRequired to 0 in
  t/db_dependent/Koha/Cash/Register/Cashup.t so the test does not
  depend on the site preference value (it previously failed when
  the syspref was set to "Require" because surpluses and deficits
  are created without a reconciliation note in this test).

Test plan:
1. Apply the patch.
2. Set CashupReconciliationNoteRequired to "Require".
3. Run prove t/db_dependent/Koha/Cash/Register/Cashup.t -- it should
   pass.
4. Set CashupReconciliationNoteRequired to "Don't require" and
   re-run -- it should still pass.
5. Run koha-qa.pl on the bug branch -- pod_coverage on
   Koha/Cash/Register/Cashup.pm and forbidden_patterns on
   cashup_summary.inc should now pass.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:37 +00:00
Martin Renvoize b7a68f1f49
Bug 40445: Add configuration options and UI enhancements
This patch adds configuration options for cashup reconciliation and
enhances the user interface with preview functionality and improved
interaction patterns.

Configuration Options:

1. CashupReconciliationNoteRequired (System Preference):
   - Optional validation requiring reconciliation notes when discrepancies exist
   - Ensures documentation of surplus/deficit causes when needed
   - Can be disabled for less stringent workflows
   - Applied at cashup completion time

2. CashupReconciliationNoteAuthorisedValue (System Preference):
   - Allows use of authorized value dropdown for reconciliation notes
   - Provides standardized note options (e.g., "Till count error", "Cash removed")
   - Falls back to free text textarea when not configured
   - Improves consistency and reporting capabilities

UI Enhancements:

1. Cashup Summary Preview:
   - Preview cashup summary for in-progress cashups
   - Shows current transaction totals before completion
   - Clearly indicates preview vs. final summary status
   - Helps staff verify amounts before committing

2. Improved User Experience:
   - Conditional note fields based on configuration
   - Authorized value dropdowns when configured
   - Clear validation messages
   - Responsive UI updates

Backend changes:
- Enhanced Koha::REST::V1::CashRegisters::Cashups with preview support
- Database atomicupdate script for system preferences
- Validation logic for required notes

Frontend changes:
- Preview modal functionality in cashup_modal.js
- Conditional rendering of note input fields
- Authorized value dropdown integration
- Enhanced summary display logic

Test plan:
1. Apply patches and run database update
2. Configure CashupReconciliationNoteRequired:
   - Enable preference
   - Perform cashup with discrepancy without note - should fail
   - Add note and retry - should succeed
   - Disable preference - verify note optional
3. Configure CashupReconciliationNoteAuthorisedValue:
   - Create AV category (e.g., CASHUP_NOTE) with values
   - Set preference to category name
   - Verify dropdown appears in cashup modal
   - Clear preference - verify textarea shown
4. Test cashup preview:
   - Start cashup on register
   - Click "Preview cashup summary"
   - Verify current totals displayed
   - Add transaction and re-preview - verify updated
   - Complete cashup - verify final summary

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:36 +00:00
Martin Renvoize 9db47a47c1
Bug 40445: Implement optional two-phase cashup workflow
This patch implements an optional two-phase cashup workflow for point
of sale operations, along with improved error handling and validation.

Two-Phase Cashup Workflow:
- Staff can initiate cashup (CASHUP_START) and complete later
- Prevents new transactions on registers with cashups in progress
- Tracks cashup sessions with start/end timestamps
- Supports both quick cashup (immediate) and staged cashup workflows
- Updates batch cashup workflow operations across multiple registers

Error Handling & Validation:
- Centralized exception handling for cashup operations
- Clear validation messages for missing parameters
- Prevents cashup when no cash transactions exist
- Improved handling of zero/negative amounts
- Informative error messages for user guidance

Negative Amount Support:
- Supports negative cashup amounts for cashup deficits
- Automatic detection when actual cash is less than expected amount
- Creates appropriate CASHUP_DEFICIT records
- UI handles both positive and negative reconciliation amounts
- Updated calculations and display logic throughout

Modal Refactoring:
- Eliminates code duplication between register.tt and registers.tt
- Centralized cashup modal functionality in cashup_modals.js
- Shared confirm_cashup.inc and trigger_cashup.inc templates
- Consistent user experience across all cashup workflows
- Improved maintainability and code organization

Backend changes (Koha::Cash::Register):
- start_cashup(): Creates CASHUP_START action to begin session
- cashup_in_progress(): Detects active cashup sessions
- add_cashup(): Enhanced with two-phase completion support
- outstanding_accountlines(): Respects cashup session boundaries

Frontend changes:
- Interactive modals for starting and completing cashups
- Real-time calculation displays
- Support for reconciliation with actual amounts
- Batch operations UI for multiple registers
- Responsive error messaging and validation feedback

Test plan:
1. Apply patches and restart services
2. Run prove t/db_dependent/Koha/Cash/Register.t
3. Run prove t/db_dependent/Koha/Cash/Register/Cashup.t
4. Test single register workflows:
   - Start cashup, add transactions, verify blocked
   - Complete cashup with reconciliation amounts
   - Test with positive, negative, and zero amounts
5. Test multi-register workflows:
   - Select multiple registers on registers page
   - Perform batch cashup operations
   - Verify each register processes correctly
6. Test error conditions:
   - Attempt cashup with no transactions
   - Attempt cashup with invalid amounts
   - Verify clear error messages displayed

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:35 +00:00
Martin Renvoize 6d5c2f463f
Bug 40445: Add cashup reconciliation functionality
Implements cashup reconciliation allowing staff to record actual cash
amounts and track surplus/deficit discrepancies.

Backend changes:
- Enhanced add_cashup() to accept actual_amount and optional notes
- Automatic CASHUP_SURPLUS/DEFICIT accountline creation for discrepancies
- Transaction handling ensures atomicity of cashup and reconciliation
- outstanding_accountlines() excludes reconciliation entries

Frontend changes:
- Interactive modal requiring actual amount entry
- Real-time surplus/deficit calculation
- Conditional note field for discrepancies (1000 char limit)
- Enhanced summary modal displays reconciliation details
- Full audit trail with timestamps and manager links

Test plan:
1. Apply all patches and restart services
2. Run prove t/db_dependent/Koha/Cash/Register.t
3. Create test transactions on a register
4. Perform cashup with balanced amount - verify no reconciliation accountlines
5. Perform cashup with surplus - verify CASHUP_SURPLUS credit created
6. Perform cashup with deficit - verify CASHUP_DEFICIT debit created
7. Add notes to reconciliation - verify stored correctly
8. Start new cashup - verify previous reconciliation excluded from outstanding
9. View cashup summary - verify reconciliation displayed prominently

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:34 +00:00
Martin Renvoize 6a38111462
Bug 40445: Add CASHUP_SURPLUS and CASHUP_DEFICIT account types
This patch adds two new system account types to support cashup reconciliation:

- CASHUP_SURPLUS (credit type): Used when actual cash found exceeds expected amount
- CASHUP_DEFICIT (debit type): Used when actual cash found is less than expected amount

Both types are system-managed and cannot be manually added by staff. They will be
automatically created during cashup processes when discrepancies are detected.

Changes include:
- Atomicupdate script for existing installations
- Mandatory YAML files for fresh installations

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:33 +00:00
Tomás Cohen Arazi 86d54c2c4a
Bug 42391: (follow-up) Add missing warning assertions for Object::store
The previous follow-up restored warn in Object::store but missed
several test files that trigger DB exceptions via ->store(). These
tests now fail Test::NoWarnings because the warning is emitted but
not expected.

This patch adds warning_like wrappers to 15 additional test files,
asserting that the expected DB exception warning is emitted.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/api/v1/biblios.t \
       t/db_dependent/Koha/Holds.t \
       t/db_dependent/api/v1/return_claims.t \
       t/db_dependent/api/v1/libraries.t \
       t/db_dependent/Koha/Booking.t \
       t/db_dependent/api/v1/patrons.t \
       t/db_dependent/SIP/Message.t \
       t/db_dependent/Koha/Patron.t \
       t/db_dependent/Koha/Acquisition/Fund.t \
       t/db_dependent/api/v1/acquisitions_orders.t \
       t/db_dependent/Koha/ILL/Requests.t \
       t/db_dependent/Koha/Patron/Relationship.t \
       t/db_dependent/Koha/Hold.t \
       t/db_dependent/Circulation/Returns.t \
       t/db_dependent/api/v1/bookings.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-27 12:12:29 +00:00
Tomás Cohen Arazi 4189d42ee6
Bug 42391: (follow-up) Restore DB exception warnings in Object::store
Bug 42391 removed the warn from Koha::Object::store when it moved
exception translation to the exception_action hook. This lost useful
debugging information from the Plack error logs.

This patch restores the warning by adding a try/catch in store() that
stringifies and warns the exception before rethrowing. The warning now
contains the translated Koha exception message (e.g. 'Broken FK
constraint') rather than the raw DBI error, since translation happens
before the exception reaches store().

Note: delete() does not warn. Ideally the logging would live in the
ExceptionMapper itself (via an injected logger), covering all code paths
uniformly. That is a larger refactor tracked separately.

Tests that assert on these warnings are updated to match the new
message format.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/Koha/Object.t \
       t/db_dependent/Koha/Objects.t \
       t/db_dependent/Koha/Schema/ExceptionMapper.t \
       t/db_dependent/Koha/Item.t \
       t/db_dependent/Koha/Suggestions.t \
       t/db_dependent/Koha/Account.t \
       t/db_dependent/Koha/ApiKey.t \
       t/db_dependent/Koha/Checkouts/ReturnClaim.t \
       t/db_dependent/Circulation/MarkIssueReturned.t \
       t/db_dependent/Suggestions.t \
       t/db_dependent/Biblio.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-21 15:40:23 +00:00
Pedro Amorim 880f33a4bb
Bug 41604: (follow-up) Wait for the page to be ready before searching in patrons_search.t
Bug 41604 made every page do a bit more work when it loads. This
Selenium test didn't wait for that work to finish before typing into
the search box, so sometimes it searched too early and got the wrong
results.

This adds a short wait after each page load so the test only searches
once the page is actually ready.

Introduced by: Bug 41604: Save user decision per column when visibility changes

Test plan:
prove t/db_dependent/selenium/patrons_search.t

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-21 15:40:19 +00:00
Pedro Amorim 037d38b00c
Bug 42772: Adjust yarn.lock
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:45:02 +00:00
Jonathan Druart f89e68c220
Bug 42772: Cypress.env => cy.env
Warning: The allowCypressEnv configuration option is enabled. This allows any browser code to read values from Cypress.env(). This is insecure and will be removed in a future major version.

1. Replace Cypress.env() calls with cy.env() (for sensitive values) or Cypress.expose() (for public configuration)
2. Set allowCypressEnv: false in your Cypress configuration to disable Cypress.env()

Learn more: https://on.cypress.io/cypress-env-migration

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: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:45:01 +00:00
Jonathan Druart 7b307694fa
Bug 42772: Remove experimentalStudio from config
The experimentalStudio option was removed in Cypress version 15.4.0.

Cypress Studio is now available for all users.

You can safely remove this option from your config.

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: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:45:00 +00:00
Jonathan Druart f357dd4103
Bug 42772: Upgrade Cypress to 15.16.0
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: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:59 +00:00
Kyle M Hall 54fc07f408
Bug 42391: (QA follow-up): Fix unit tests
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:58 +00:00
Kyle M Hall 22d988e8b4
Bug 42391: (QA follow-up): Clarify exception_action ref-passthrough comment
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:57 +00:00
Kyle M Hall 9c04c7bbb4
Bug 42391: (QA follow-up): Remove unused Try::Tiny from Koha::Item
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:56 +00:00
Tomás Cohen Arazi 62e003b88f
Bug 42391: (follow-up) Rename ExceptionTranslator to ExceptionMapper
The Koha::Schema::Util:: namespace adds unnecessary nesting for a
single-purpose class. This follow-up moves it to
Koha::Schema::ExceptionMapper, which sits alongside Koha::Schema
itself — a better fit given it's tightly coupled to the Schema's
exception_action hook.

Changes:
- Koha::Schema::Util::ExceptionTranslator → Koha::Schema::ExceptionMapper
- Update Koha::Schema to use the new module
- Move test file accordingly
- Remove now-empty Koha/Schema/Util/ directory

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:55 +00:00
Tomás Cohen Arazi 033cfd772c
Bug 42391: (follow-up) Add NotNull, NotInStorage and UnhandledDBError translations
Extend the ExceptionTranslator to handle two additional MySQL error
patterns:

- NOT NULL violations ('Column X cannot be null' and 'X doesn't have
  a default value') → Koha::Exceptions::Object::NotNull
- Not in database errors → Koha::Exceptions::Object::NotInStorage

Add Koha::Exceptions::Object::NotNull exception class with a property
field identifying the offending column.

Add Koha::Exceptions::Object::UnhandledDBError as a catch-all for DBI
errors that don't match any known pattern. This closes the gap where
unmatched MySQL errors could leak as DBIx::Class::Exception to callers
(e.g. templates that no longer catch that type).

The exception_action handler now distinguishes three cases:
- Koha exception objects (from txn_do re-throws): passed through via
  'die $msg' to preserve the original exception type. Without this,
  txn_do's rollback-and-rethrow would stringify the Koha exception
  and wrap it in UnhandledDBError, losing the specific type.
- DBI errors (prefixed 'DBI Exception:'): translated if possible,
  otherwise wrapped in UnhandledDBError.
- DBIC internal errors (e.g. 'Not in database'): translated if
  possible, otherwise thrown as DBIx::Class::Exception.

Note: NotInStorage translation works for update operations but not for
delete-on-detached-Row where DBIC bypasses the Schema's exception_action.
Those cases remain DBIx::Class::Exception.

Update tests:
- OAIServers.t, Z3950Servers.t: assert NotNull with property check
- Hold.t: assert NotNull, remove warn suppression hack
- Objects.t: update-not-in-storage cases assert NotInStorage
- Upload.t: delete-not-in-storage stays DBIx::Class::Exception

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:54 +00:00
Tomás Cohen Arazi face3c96d0
Bug 42391: (follow-up) Fix Objects.t case 3 to test FK deletion properly
The 'Delete a patron that cannot be deleted' test (case 3) was using a
stale $r_us object from case 2 (not in storage), so it was not actually
testing FK constraint deletion. The assertion on DBIx::Class::Exception
passed by accident.

This follow-up:
- Fetches a fresh Koha::Patron object for case 3
- Clears $e_us/$e_them before the new test
- Asserts Koha::Exceptions::Object::FKConstraintDeletion, which is what
  exception_action now produces for FK deletion errors

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:53 +00:00
Tomás Cohen Arazi 66b738f1eb
Bug 42391: Use exception_action to translate DBIC exceptions at the source
DBIx::Class provides an exception_action hook that intercepts every
throw_exception call before it becomes a DBIx::Class::Exception. By
installing this hook on Koha::Schema via a connection() override, all
database exceptions are automatically translated into Koha exceptions
at the source.

This eliminates the need for translate_exception catch blocks in every
caller:
- Remove try/catch from Koha::Object::store
- Remove try/catch from Koha::Object::delete
- Remove try/catch from Koha::Item::add_to_bundle
- Remove translate_exception convenience method from Koha::Schema
- Remove safe_do (already removed in previous follow-up)

ExceptionTranslator is refactored to work on plain error message strings
instead of DBIx::Class::Exception objects, since exception_action
receives the message before wrapping.

Unmatched errors fall through to DBIx::Class::Exception->throw() as
the default, preserving backward compatibility.

The warn $_->{msg} that was in Object::store is no longer needed since
exceptions are translated before they propagate. Tests that asserted on
that warning (via warning_like) are updated to use throws_ok directly.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Schema/Util/ExceptionTranslator.t
=> SUCCESS: Tests pass!
  k$ prove t/db_dependent/Koha/Object.t
=> SUCCESS: Tests pass!
  k$ prove t/db_dependent/Koha/Item.t
=> SUCCESS: Tests pass!
  k$ prove t/db_dependent/Koha/Patron/Modifications.t
=> SUCCESS: Tests pass!
3. Sign off :-D

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:52 +00:00
Owen Leonard 8bc90cbb5e
Bug 42440: Remove event attributes from labeled MARC detail template
This patch removes a redundant "onchange" event attribute from the
framework selection dropdown. This event is already registered in the JS
block.

The patch also does a little bit of cleanup to the JS: Combining two
<script> tags which don't need to be separate and moving a function
outside of the $(document).ready block.

To test, apply the patch and search for a bibliographic record in the
catalog.

- From the detail page, click the "Labeled MARC" tag in the sidebar (you
  may need to enable the "viewLabeledMARC" preference).
- Try changing the "With framework" selection. The page should reload
  with the selected framework.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:51 +00:00
Andrew Fuerste Henry 22621e24cd
Bug 36762: [QA Follow-up]: Tidy branchtransfers.pl
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:50 +00:00
Emmi Takkinen ed992358bf
Bug 36762: Allow to ignore hold when creating a transfer
When one tries to transfer an item that could fill a pending hold
to a library that is not the holds pickup library, Koha gives only
a option to transfer item to pickup library. This patch adds
new option to ignore hold and transfer item to the library user
selected in transfer tool.

To test:
1. Log into Library A.
2. Find or create a biblio with 1 holdable item at Library A.
3. Place a hold for the biblio with pickup at Library B.
4. Navigate to Circulation > Transfers.
5. Attempt to transfer item to Library C.
=> Note that Koha only allows to transfer item to Library B,
cancel hold and attempt transfer or return to the transfer tool.
6. Apply this patch.
7. Attempt to transfer item again.
=> Note that there is now an option to ignore the hold and transfer
item to Library C.
=> When option to transfer item to the Library C is selected, confirm
that Koha tells that transfer was made to Library C and a new transfer
was made to database.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:49 +00:00
Emmi Takkinen 2994d2c484
Bug 36762: Fix setting transfer for hold via branchtransfers.pl
When one tries to transfer an item that could fill a pending hold
to a library that is not the holds pickup library, Koha gives only
a option to transfer item to pickup library. When this option
is chosen, Koha tells that item is being transfered to selected
library not to pickup library but fails to create the transfer.
This patch fixes this by setting op variable as "cud-transfer"
and tobranchcd as holds pickup library so that transfer is actually
made and target library is displaued correctly.

To test:
1. Log into Library A.
2. Find or create a biblio with 1 holdable item at Library A.
3. Place a hold for pickup at Library B.
4. Navigate to Circulation > Transfers.
5. Attempt to send your item to Library C.
6. Koha gives you three options. Pick the first one:
"Transfer to Library B."
7. Koha tells you it has transferred the item to Library C.
8. Go to detail.pl for your title, confirm the holdings table
says "there is an item level hold" on your item.
9. Click to the Holds tab for your title, confirm the hold claims
to be in transit.
10. Query the branchtransfers table
SELECT * FROM branchtransfers WHERE itemnumber = [your item]
=> Note that no new transfer was made.
11. Query reserves SELECT * FROM reserves WHERE biblionumber = [your biblio]
=> Confirm your hold has found=T, itemnumber=[your item], and item_level_hold=0
12. Revert trasfer from biblios holds page.
13. Apply this patch.
14. Attempt the transfer again.
=> Koha should now tell that transfer was made to Libary B.
15. Check branchtransfers table again.
=> New transfer should have been made to Library B.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:48 +00:00
Martin Renvoize 3abe3f1485
Bug 40206: (QA follow-up) Fix JS error and misleading message on disabled curbside pickups page
The early-exit added for the CurbsidePickup syspref check left
policy/curbside_pickups unset, but intranet-bottom.inc's jsinclude
still referenced them unconditionally, causing a client-side
TypeError on every render of the disabled state. Guard the whole
jsinclude block with disabled_for_branch.

Also distinguish the two reasons the page can be disabled: the
CurbsidePickup syspref being off globally vs. this branch simply
not having an enabled curbside pickup policy configured. The
message and link now point staff to the correct admin page for
each case, instead of always suggesting the syspref (which may
already be on).

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:47 +00:00
Tomas Cohen Arazi c68bacc5c2
Bug 40206: Fix wrong layout in curbside pickups when 'CircSidebar' enabled
This patch solves the following issues:

* Template flow issue: the template called STOP which prevented the
  WRAPPER to complete and affected how things were rendered.
* Redirect to 404 if feature disabled. This is common practice in other
  modules. The fact things can be executed even with the feature
  disabled is not ok (syspref).
* Shortcircuit earlier. Similar to the above, if the feature is
  disabled for the current branch, no DB queries and stuff should take
  place. Notice the lack of checks actually makes the controller perform
  the actions. It is just that it is not displayed afterwards!

To test:
1. Enable the `CurbsidePickup` syspref.
2. 'Activate' the `CircSidebar` syspref.
3. Be on a branch that doesn't have them enabled
4. Go to Circulation > Check in
   => SUCCESS: A nice left-hand sidebar is displayed with access to
      Circulation actions
5. Click on 'Curbside pickups'
   => FAIL: The sidebar doesn't display. boo!
6. Apply this patch
7. Restart plack
   $ ktd --shell
   $ koha-plack --restart kohadev
8. Repeat 4
   => SUCCESS: It renders much better!
9. Sign off :-D

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 12:44:43 +00:00
Pedro Amorim 2f227048ae
Bug 42280: (RM follow-up) Tidy categories.tt
Leftover untidy JS from bug 26355 (multi-line jQuery .on() calls,
missing semicolon) in the clone/clear message handlers.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:16:04 +00:00
Jan Kissig a4a53ea03c
Bug 42280: (QA follow-up) Split JS in uage_statistics to TT and non TT block. Auto tidy
This splits remaining JS into a block with and without TT interpolation. Auto tidy

Small changes on add_to_map() call:

To test:
a) go to Administration -> Shar eyour usage statistics
b) in dev console check Koha.prefs.UsageStatsGeolocation is [] when no coordinates are set
c) add some coordinates to main library (first Geolocation input field) and others
d) check that Main and other libraries are drwan on the map when reentering usage statistics page
e) No console errors should be shown

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:16:03 +00:00
Jan Kissig 8de6874de5
Bug 42280: (QA follow-up) Split JS in transfer_limits to TT and non TT block. Auto tidy
This splits remaining JS into a block with and without TT interpolation. Auto tidy

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:16:02 +00:00
Jan Kissig 5ce434644c
Bug 42280: (QA follow-up) Split JS in restrictions to TT and non TT block. Auto tidy
This splits remaining JS into a block with and without TT interpolation. Auto tidy

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:16:01 +00:00
Jan Kissig 1987d0a930
Bug 42280: (QA follow-up) Split JS in preferences to TT and non TT block. Auto tidy
This splits remaining JS into a block with and without TT interpolation. Auto tidy

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:16:00 +00:00
Jan Kissig 1153112951
Bug 42280: (QA follow-up) Split JS in matching-rules to TT and non TT block. Auto tidy
This splits remaining JS into a block with and without TT interpolation. Auto tidy

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:15:59 +00:00
Jan Kissig c2f1576a3d
Bug 42280: (QA follow-up) Split JS in curbside_pickup to TT and non TT block. Auto tidy
This splits remaining JS into a block with and without TT interpolation. Auto tidy

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:15:58 +00:00
Jonathan Druart f52abf6d79
Bug 42280: Tidy all script tags - admin (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:15:57 +00:00
Jonathan Druart 70bb50988b
Bug 42280: Tidy all script tags - admin
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the administration module.

The 2 main changes are in the following files:
* aqplan.tt
"0" should be correctly formatted
* categories.tt
trivial change about table settings

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:15:56 +00:00
Jonathan Druart 0de23cde12
Bug 42291: Prevent XSS
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:15:54 +00:00
Jan Kissig d194215dfe
Bug 42291: (follow-up) Move remaining JS to non TT script block
This removes all TT interpolations from the build_dropdown function and moves it to the non TT script block.

To test:
a) create several private or public lists via /cgi-bin/koha/virtualshelves/shelves.pl
b) either the number of private or public list should be > 10 to
c) add some items to a list
d) on this lists content site click 'Add to' to open drop down menu
e) the menu should offer up to 10 private or 10 public list options and a [ more lists ] option

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:15:53 +00:00
Jonathan Druart 56036ac098
Bug 42291: Tidy all script tags - shelves (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:15:52 +00:00
Jonathan Druart f73b0db9ae
Bug 42291: Tidy all script tags - shelves
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the virtual shelves module.

Trivial changes, the existing JS variables are moved in a separate
script tag.
We could have gone further with the list of shelves (the build_dropdown
function) , but that's for another day...

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:15:51 +00:00
Jonathan Druart 9ac2f291b3
Bug 41061: Remove unused calendar JS functions
Since the introduction of Maskito by bug 33703 we do no longer need to
valid dates when the flatpickr widget is closed.

This patch removes the following JS function from staff and OPAC:
* is_valid_date
* validate_date
* get_dateformat_str

Test plan:
0. Do not apply this patch and confirm that the date string received by
   those functions has already been correctly formatted
1. Apply the patch and git grep to make sure no occurrence exists in the
   code
2. Confirm that you are still able to select dates with flatpickr

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:15:50 +00:00
Pedro Amorim c798a0c49b
Bug 41084: (RM follow-up): Remove atomicupdate file
Should have been part of e5aa0c0

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-20 09:15:49 +00:00
Lucas Gass 1265f90523
Bug 43005: Remove messaging preferences check
1. Go to Patron record -> Details
2. Under "Library use" click 'Edit'
3. From here change the patron category, no pop-up
4. APPLY PATCH and try again
5. Now you should see the pop-up modal

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:20 +00:00
Kyle M Hall 506f6ed2a2
Bug 43034: Fix t/db_dependent/Koha/EDI.t failure when SearchEngine is Elasticsearch
Bug 21820 made the Zebra queue update conditional on the new
ElasticsearchEnableZebraQueue system preference, which defaults to off.
The 'basic_quote_processing' subtest in t/db_dependent/Koha/EDI.t counts
the log lines process_quote emits, and two of those are the
"specialUpdate biblioserver" traces that ModZebra logs only when the
Zebra queue path runs. When the SearchEngine preference is set to
Elasticsearch, that path no longer runs by default, so the subtest now
records 6 log lines instead of 8 and fails:

    #   Failed test '8 log lines recorded for passed quote file'
    #          got: '6'
    #     expected: '8'
    #   Failed test 'Trace recorded biblioserver index'

Test Plan:
1) Set the SearchEngine system preference to 'Elasticsearch'
2) prove t/db_dependent/Koha/EDI.t
3) Note the basic_quote_processing subtest fails with "got: '6', expected: '8'"
4) Apply this patch
5) prove t/db_dependent/Koha/EDI.t
6) Note the tests pass!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:19 +00:00
Martin Renvoize 77fc25f184
Bug 42915: (QA follow-up) Remove unused filters_options variable
filters_options was introduced with the coded_values library filtering
patch but never referenced anywhere in the file.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:18 +00:00
Martin Renvoize 1cf8f9ffc5
Bug 42915: (QA follow-up) Restore fallback message for holds with no patron data
The "Add patron names to data in column def in holds table" patch
dropped the null-patron handling in the patron column's render
function, so a hold row whose patron embed comes back null (e.g. a
user without permission to view the patron on that hold, per Bug
23269's test plan) now renders a blank cell instead of a useful
message.

Restore the fallback, sourced from `all_libraries` (populated by this
same table's include) rather than the separate `libraries` global,
and check `row.patron` directly instead of the now-composite `data`
column key.

To test:
1. Have holds on a bib from patrons the logged in user cannot see
   (e.g. IndependentBranches, or a reduced permission set)
2. View the record's Holds tab
3. Confirm rows with no visible patron show "A patron from library
   <library>" instead of a blank Patron cell

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:17 +00:00
Martin Renvoize e2685f88c9
Bug 42915: (QA follow-up) Declare all_libraries once, unconditionally, in request.tt
holds_table.inc is INCLUDEd in a loop from reserve/request.tt whenever
HoldsSplitQueue splits the holds queue by branch/itemtype, or when
holds for more than one biblio are shown at once (multi_hold). Each
inclusion appended its own <script> block declaring `all_libraries`,
and since top-level `let`/`const` in separate inline <script> tags
share the page's global lexical scope, the second and subsequent
occurrences threw "Identifier 'all_libraries' has already been
declared", breaking those script blocks and the pickup-library
filtering that depends on `all_libraries` being set.

Worse, that script block is only emitted when a biblio's holds are
actually rendered (biblioloo.total_holds > 0), but holds.js is loaded
unconditionally by reserve/request.tt and reads `all_libraries` at
top-level parse time. Placing the very first hold on a title (0
existing holds) therefore threw:

    ReferenceError: all_libraries is not defined

breaking every hold-management handler defined below that line in
holds.js.

Fix both by declaring `all_libraries` exactly once, unconditionally,
in request.tt's existing page-global script block (alongside the
other unconditional globals such as HidePatronName and
HoldsSplitQueue), before Asset.js("js/holds.js") loads, instead of
duplicating it inside the per-title/per-split holds_table.inc
include.

To test:
1. Search for a title with no existing holds and start placing one
2. Without this patch: browser console shows "ReferenceError:
   all_libraries is not defined" as soon as the page loads
3. With this patch: no console errors
4. Set HoldsSplitQueue to "pickup library", place holds on a title
   for two different pickup libraries, confirm both split tables
   render with working patron-name and pickup-library filtering and
   no console errors
5. Confirm the patron's own Holds tab (moremember.pl/circulation.pl)
   still loads without console errors

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:15 +00:00
Brendan Lawlor 6532c4660a
Bug 42915: (follow-up) Fix holds table display on patron record holds tab
To test:
1. Have a patron with multiple holds
2. Go to the patron record's hold tab
3. Confirm the table loads and no errors are logged to the console

Sponsored-by: CLAMS
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:15 +00:00
Brendan Lawlor e4af8e73f3
Bug 42915: Add patron names to data in column def in holds table
This patch adds the patron firstname and surname to the searchable
data in the patron column definition for the holds table and
removes unnecessary code from the render function.
This enables searching holds by patron names.

To test:
1. Have holds on a bib for different patrons and pickup locations
2. Try filtering by a patron name
3. See 'No matching records found'
4. Apply patch and repeat step 2
5. Filtering by patron name works!
6. Confirm filtering by cardnumber still works too
7. Set system preference HidePatronName to 'Don't show'
8. Confirm searching by patron names doesn't work ;)

Sponsored-by: CLAMS
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:13 +00:00
Brendan Lawlor e4a9f1f531
Bug 42915: Use coded value for library filtering in holds table
This patch adds coded_values for libraries to the holds table
and sets the data type in the pickup library column definition.
This enables searching holds by pickup location.

1. Have holds on a bib for different patrons and pickup locations
2. Try filtering by a pickup library name or branchcode
3. See 'No matching records found'
4. Apply patch and repeat step 2
5. Filtering works!
6. Note: if two libraries share the same beginning text
    both results are returned :)
    For example: 'Fair' returns holds for pickup at Fairview and Fairfield

Sponsored-by: CLAMS
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:12 +00:00
Pedro Amorim e5aa0c0737
Bug 41084: DBRev 26.06.00.004
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:11 +00:00
Martin Renvoize 52358cf3b6
Bug 41084: (QA follow-up) Add new pref to UsageStats
Add the new preference to UsageStats for HEA

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:10 +00:00
Katrin Fischer 667df54aed
Bug 41084: Re-format database update for new way of addind system preferences
... also adds say_success.

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:09 +00:00
Tomás Cohen Arazi 5d8af22026
Bug 41084: Allow utf8 export format when EnableZotero is enabled
When the EnableZotero system preference is enabled, allow the utf8 format
in opac-export.pl regardless of the OpacExportOptions settings.

This enables Zotero integration by ensuring the required UTF-8 encoded
MARC format is always available when the feature is enabled.

To test:
1. Install Zotero (https://www.zotero.org)
2. Install the browser extension (I did in in Chrome, not sure it exists for FF)
3. Perform a search in your koha instance. e.g. http://kohadev.localhost/cgi-bin/koha/opac-search.pl?idx=&q=agrovoc&weight_search=1
4. Open the Zotero extension
=> SUCCESS: It lists the search results for saving them
5. Choose one and save
=> FAIL: It says there was an error saving
6. Apply this patches
7. Run:
   $ ktd --shell
  k$ updatedatabase
8. Make sure EnableZotero is enabled
9. Repeat 3-5
=> SUCCESS: All good this time
10. Sign off :-D

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:08 +00:00
Tomás Cohen Arazi 5b3baf4170
Bug 41084: Add EnableZotero system preference
This system preference will be used to enable opac-export.pl?format=utf8
regardless of the OpacExportOptions values, specifically for Zotero
integration support.

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:07 +00:00
Jonathan Druart 674749c621
Bug 42801: Fix race condition in ERM/UsageReports_spec.ts
cy.get("#report_builder").contains("Submit").click();
cy.intercept("GET", "/api/v1/erm/eUsage/monthly_report/*").as(
    "reportRequest"
);
cy.wait("@reportRequest");

We should intercept before the click.

Test plan:
yarn cypress run --spec t/cypress/integration/ERM/UsageReports_spec.ts
Run it several times and confirm that it always passes

The failure was:
    <testcase name="Custom reports Should show the Display by year filter when filter is required" time="0.000" classname="Should show the Display by year filter when filter is required">
      <failure message="Timed out retrying after 10000ms: `cy.wait()` timed out waiting `10000ms` for the 1st request to the route: `reportRequest`. No request ever occurred.

https://on.cypress.io/wait" type="CypressError"><![CDATA[CypressError: Timed out retrying after 10000ms: `cy.wait()` timed out waiting `10000ms` for the 1st request to the route: `reportRequest`. No request ever occurred.
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:06 +00:00
Martin Renvoize 393c9478a0
Bug 42799: (QA follow-up) Close scroll listener leak in apply_sticky
apply_sticky is invoked from a watch() in Toolbar.vue every time the
toolbar element (re)mounts. The scroll listener it registers was never
removed, so long-lived SPA modules (ERM, ILL, Acquisitions,
Preservation, SIP2) that mount/unmount this Toolbar as the user
navigates between records leaked another window-level scroll listener
on every remount.

An IntersectionObserver + sentinel node was tried instead, since it's
the standard technique and doesn't poll on every scroll frame, but
verified against KTD/Cypress it fails
t/cypress/integration/Toolbar_spec.ts consistently: the observer's
callback fires once on attach and never fires again after
cy.scrollTo("bottom"), even though the sentinel is confirmed (via
instrumentation) to have scrolled fully out of the viewport. The
scroll-listener approach passes the same spec reliably across repeated
runs. So the scroll listener stays; apply_sticky now returns a
disconnect() callback and Toolbar.vue calls it before re-applying and
on unmount, closing the leak without touching detection behaviour.

Test plan:
1. Rebuild assets (yarn build) and confirm:
     yarn cypress run --spec t/cypress/integration/Toolbar_spec.ts
   passes consistently across several runs.
2. Manually confirm sticky toolbars in ERM, Acquisitions (vendors),
   ILL, Preservation and SIP2 still float correctly on scroll, and that
   navigating between several records doesn't accumulate extra "scroll"
   listeners on window (DevTools -> getEventListeners(window)).

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:05 +00:00
Jonathan Druart 35d4eebbb0
Bug 42799: Replace IntersectionObserver with scroll listener for sticky behaviour
Assisted-by: Mistral Vibe <vibe@mistral.ai>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:04 +00:00
Jonathan Druart 74b820394d
Bug 42799: Fix sticky behavior by using a sentinel node
Highlighted by the Cypress upgrade (bug 42772): the sticky element is always
visible and IntersectionObserver rely on the visibility of the element.

Cypress test Toolbar_spec.ts use scrollTo("bottom") to test the
behaviour of the sticky toolbar. We test that the "floating" class is
correctly added to the element. To do that we rely on
IntersectionObserver that will check the visibility of the element. But
the sticky element is always visible.
I actually do not recreate the problem when using the UI but the current
code may be broken in some browsers (or new/future versions).

To fix this problem we insert a new "sentinel" element into the DOM and
rely on its visibility to toggle the "floating" class.

Test plan:
Confirm that the sticky toolbar behaviour is the same after this patch
is applied, ie. that the "floating" class is added when we scroll down.

On top of bug 42772 confirm that the following command passes (run it
several times):
  yarn cypress run --spec t/cypress/integration/Toolbar_spec.ts

QA: Note that the apply_sticky function takes only one node in
parameter, that's why it has been renamed nodes => node

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:03 +00:00
Pedro Amorim 105359b512
Revert "Bug 41717: Bump PDF::Reuse and PDF::Reuse::Barcode versions"
This reverts commit 8b4d69b19d.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-17 13:49:00 +00:00
Jonathan Druart ff8bdc502e
Bug 42979: Prevent failure on My8
In MySQL8 the order on the PK is not preserved. We need to explicitely
pass it.

Test plan:
export DB_IMAGE=mysql:8.0;
then startup ktd
% prove t/db_dependent/api/v1/biblios.t
should pass with this patch
Without this patch it failed with:

13:00:29         #   Failed test 'exact match for JSON Pointer "/4/item_id"'
13:00:29         #   at t/db_dependent/api/v1/biblios.t line 276.
13:00:34         #          got: '3420'
13:00:36         #     expected: '3417'
13:00:37
13:00:38         #   Failed test 'Withdrawn comes after lost'
13:00:57         #   at t/db_dependent/api/v1/biblios.t line 276.
13:00:59         #          got: '3417'
13:01:04         #     expected: '3420'
13:01:05         # Looks like you failed 2 tests of 8.
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:23 +00:00
Nick Clemens c81d27de08
Bug 35948: Add jobs_unfinished_days and jobs_unfinished_type to cleanup_database.pl
This patch adds two new parameters to control the deletion of background jobs that did not complete.
Some libraries may want to keep a failed job longer than a completed one for debugging reasons.

To test:
0 - Apply patch
1 - Make some old failed jobs:
    INSERT INTO background_jobs (status,type,enqueued_on) VALUES
    ('started','update_elastic_index','2020-01-01'),
    ('failed','update_elastic_index','2026-01-01'),
    ('started','other_type','2021-01-01'),
    ('failed','other_type','2026-01-01');
2 - Confirm defaults of 365 and update_elastic_index for new params:
    perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days
3 - Should indicate 1 job older than 365 of type update_elastic_index would have been deleted
4 - Confirm days param works
    perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days 30
5 - Shoudl find 2 elastic jobs
6 - Confirm type param
    perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days --jobs-unfinished-type=other_type
7 - Should find 1
8 - Confirm type and days
    perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days 30 --jobs-unfinished-type=other_type
9 - Should find 2
10 - Confirm repeated type
     perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days=30 --jobs-unfinished-type=other_type --jobs-unfinished-type=update_elastic_index
11 - Finds 4
12 - Confirm deletion
     perl misc/cronjobs/cleanup_database.pl -v --jobs-unfinished-days=30 --jobs-unfinished-type=other_type --jobs-unfinished-type=update_elastic_index --confirm
13 - All your made up jobs are removed

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:22 +00:00
Adolfo Rodríguez f81ec88697
Bug 42999: Fix $valid_items flag contamination in reserve/request.pl
$valid_items was set inside the per-item availability check but used as
the gate of a separate if block, causing it to carry over across loop
iterations. Once any item set it to 1, all subsequent items entered the
if ($valid_items) branch and received $item->{available} = 1 regardless
of their individual availability.

Fix: merge the two consecutive if blocks into one so the pickup-location
logic only executes when the current item individually passes all
availability conditions. $valid_items continues to serve its correct
post-loop role (line 661: none_available_override).

Regression introduced by Bug 38412 (commit 1900effc9b).

Steps to follow:
1. Create a biblio with at least two items: one with a holdable item
   type and one with a not-for-loan item type (itemtypes.notforloan=1).
   The not-for-loan item must have a higher itemnumber than at least
   one holdable item (i.e. it is processed later in the loop).
2. Open reserve/request.pl?biblionumber=X&borrowernumber=Y
3. Observe that the not-for-loan item shows a radio button and a pickup
   location dropdown despite the Information column showing "Not for
   loan (Itemtype not for loan)" — bug confirmed.
4. Apply this patch and restart services. If running under Plack,
   restarting Plack is required for the change to take effect.
5. Reload the place-hold page.
6. Verify the not-for-loan item now shows "X Not for loan" without a
   radio button or pickup location dropdown.
7. Verify the holdable items still show radio buttons and pickup
   location dropdowns correctly.

Signed-off-by: Adolfo Rodríguez <adolfo.rodriguez@xercode.es>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:21 +00:00
Jan Kissig 41cfe9de0c
Bug 42874: Fix missing index param on z3950_auth_search
This patch will enable the forwarding of index param in all z3950_auth_search forms to keep the reference which auth field to fill in addbiblio.

Testplan:
a) edit f.e. 262: /cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=262
b) go to tab 7 and create a new 700 field (icon next to ADDED ENTRY--PERSONAL NAME)
c) click on the icon next to Personal name input field to open Authority search *
d) click create new authority (Adding authority Personal Name) *
e) click Z39.50/SRU search *
f) search an author f.e. Name (any): Dalei
g) on the result list every 'Import' button still has index (via hovering the import button) - but do not click import yet.
h) click on 'Go' to page 1 - see result list again. The 'Import' buttons do not have index param anymore
i) close that search window and go back to page 'Adding authority Personal Name'
j) click Z39.50/SRU search again *
k) search again for an author f.e. Name (any): Dalei
l) click 'Try another search'
m) check that new URL does not have index param anymore
n) search again - the results will not have an index param

Any imported authority will not be set/linked in the biblio that was initially edited.

p) apply patch and redo steps a) - n)
q) Import an author either via a 'Try another search' or via a result on a different page ('Go' button)
r) save authority
s) check that authority is added in your biblio in field 700.

* check that the URL in the popup window has a param like &index=tag_700_subfield_a_13631_9534947903 as this is the reference to the authority you want to add in the biblio.

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:20 +00:00
Pedro Amorim b856177781
Bug 43069: Add tests
1) Set system preference 'PatronDuplicateMatchingAddFields' to empty (no fields ticked)
2) Go to <staff_url>/cgi-bin/koha/members/memberentry.pl?op=add and fill in the mandatory fields for a brand new patron (one that definitely doesn't exist yet)
3) Click 'Save'
4) Notice you get the 'Duplicate patron record?' dialog, even though no patron like this exists
5) Apply the patch. Restart plack
6) Repeat steps 2-3 with another new patron
7) Confirm the patron is created fine this time, no false duplicate error
8) Also try setting 'PatronDuplicateMatchingAddFields' to a field (e.g. 'city') and submit a new patron with that field left blank - confirm no false duplicate there either
9) Run:
prove t/db_dependent/Koha/Patrons.t

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:19 +00:00
Pedro Amorim 22221feaa2
Bug 43069: Don't match every patron when no duplicate-match fields apply
check_for_existing_matches() called Koha::Patrons->search(undef) when
PatronDuplicateMatchingAddFields left no usable field to match on,
which matches every patron instead of none.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:18 +00:00
Jonathan Druart e87732a1b6
Bug 42933: Reinstate license.status as a required field
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:17 +00:00
Jonathan Druart 0299c6f3c3
Bug 42933: Add a Cypress test
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:15 +00:00
Nick Clemens 59ac731b1d
Bug 43043: Pass logged in branchcode to OpacMySummaryNote
This patch uses the logged in branchcode to find the correct entries

To test:
1 - Create two new HTML customizations - OpacMySummaryNote - one for all libraries, one for a specific library
2 - Log in to opac as a user from the specific library
3 - Only see the all libaries note
4 - Apply patch
5 - Reload
6 - See both of the notes as expected!
7 - Sign off!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:14 +00:00
Laura_Escamilla 9a875d0202
Bug 42919: Retain suggestion form values when duplicate warning is shown
To test:

1. Make a suggestion that will trigger a duplicate warning, in KTD I like "Clothes, clothes, clothes, music, music, music, boys, boys, boys".
2. Fill out some other details, like notes and public notes.
3. The warning says "Click on "Confirm your suggestion" to ignore this message."
4. Notice none of the details are still there.
5. Apply the patch
6. Repeat steps 1-3. Notice that the details remain now after you get the warning message.
7. Submit the form to ensure all details are saved correctly.
8. Sign off and have a lovely day. :D

Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:13 +00:00
Baptiste Wojtkowski cb534eb7d3
Bug 8127: Fix filters use in 'Most-circulated items report'
Filters are broken for many reasons. Previous patch contains some
(little) tidying.
There are 3 parts in this patch:
1 - The column name is incorrectly named in the dropdown for "branch",
  it is added in this patch
2 - There is a definition for 'Day' and 'Month', but not for 'Week', it
is also added in this patch
3 - Dbh variables are passed in the string definition AND in the call. I
removed them from the string definition since it violates the coding
guidelines.

Test plan:

1. On an fresh ktd db, run the script provided here (https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=8127#c3)  to fill the database with checkins/checkouts:

Visit http://your_koha:8081/cgi-bin/koha/reports/cat_issues_top.pl
2. Run a baseline test with following params:
Checkout date from 01/01/2020
Checkout date to: tomorrow
Library: Any library
Fill nothing else

Notice it is works

3. Test with some limits (Library, Week, and any other), notice you encounter errors on Library and Week, and that any other is empty, without error.

4. Apply patch
5. Rerun 2-3 and notice you now have results. Check with each possible limit, limiting to 5. Test once with 10 as a limit.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:12 +00:00
Baptiste Wojtkowski 0b04d6f76c
Bug 8127: Remove useless commented warns
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:11 +00:00
Lucas Gass c997f83cdf
Bug 43001: Restore non-priority hold text to reserve/request.pl
To test:

1. Place a item level hold, making sure to mark it as "Non priority hold"
2. After placing the hold, on reserve/request.pl look at the "Details" column
3. Notice the hold is missing "Non priority hold" text in the details column
4. APPLY PATCH
5. Look again at reserve/request.pl and now the "Non priority hold" text should be restored to 'Details column'

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:09 +00:00
Chris Nighswonger 8b4d69b19d
Bug 41717: Bump PDF::Reuse and PDF::Reuse::Barcode versions
PDF::Reuse 0.43 fixes a TTFont/docProxy lifecycle bug that caused
prEnd() to crash when TrueType fonts were used.

PDF::Reuse::Barcode 0.09 is the current stable release.

Testing plan:

- Start ktd, note older perl modules versions under About Koha
- Apply patch
- ktd --shell
- sudo cpan -i PDF:Reuse; sudo cpan -i PDF:Reuse:Barcode
- Let cpan auto set itself up (not sure if sudo is needed above)
- `restart_all`
- Revisit About Koha; module versions should be 0.43 & 0.09 respectively

Signed-off-by: Eric Phetteplace <ephetteplace@cca.edu>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:08 +00:00
Martin Renvoize 111637445a
Bug 42868: (follow-up) Add regression test for non-UTC library timezone
The bug this fixed only manifests when the library's configured
timezone (C4::Context->tz) is not UTC: Koha::Object::_recursive_fixup
converts an incoming RFC3339 instant to that timezone before storing
it in the timezone-naive 'bookings.start_date'/'end_date' columns, so
a UTC-anchored boundary shifts by the timezone offset on write.

None of the existing Cypress specs catch this, because they never run
with the browser/test-runner timezone diverging from the configured
library timezone - both always come from the same container clock.

Add a Perl-level API test that mocks C4::Context->tz to a non-UTC zone
(America/New_York) and asserts the boundaries sent by place_booking.js
round-trip to the correct library-local date, pinning this regression
independently of any particular environment's timezone.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:08 +00:00
Martin Renvoize 5a7c328475
Bug 42868: (follow-up) Fix stale comment referencing removed UTC boundary logic
The comment above the day-boundary calculation still described the
previous dayjs.utc() approach, which was replaced by dayjs.tz() in the
parent commit. Update it to describe the current behaviour so it
doesn't mislead future maintainers into re-introducing the timezone
shift bug this patch fixed.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:07 +00:00
Lucas Gass 084660a6df
Bug 42868: Use local timezone instead of UTC when calculating start and end dates
To test:
1 - Edit koha-conf.xml
2 - Set timezone:
    <timezone>America/New_York</timezone>
3 - In Koha, enable bookings syspref
4 - Find a record, click the items tab, make an item bookable
5 - Place new booking, choose a date range
6 - View the bookings tab - note the booking extends into the date before
7- Check the DB, the start_date and end_date are wrong
8. APPLY PATCH, restart_all, clear browsewr cache
9. Place a new booking.
10. MariaDB [koha_kohadev]> select * from bookings WHERE booking_id={new booking}
11. Notice the start time should be {start_date} 00:00:00 and the end time should be {end_date} 23:59:59

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:06 +00:00
Jonathan Druart 50a5dcc4d4
Bug 42349: Adjust tests
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:05 +00:00
Jonathan Druart 8f98b493ae
Bug 42349: Fix filtering items by recalls
On the holdings table the filter by status=recalls is not working correctly.

Test plan:
Add a recall on an item of a bibliographic record that has several
items.
Filter the items table by status=recall
=> Without this patch all items are displayed
=> With this patch applied only the item with a recall is displayed

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Aleisha Amohia <aleisha@catalyst.net.nz>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:03 +00:00
Jonathan Druart 9544e68acc
Bug 42066: Add a Cypress test
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:05:02 +00:00
Jonathan Druart 06bac7582f
Bug 42066: Prevent 403 after an expired OPAC session
If a session has expired, the login form will get an empty CSRF token
and the login form will return 403

To recreate:
hit /cgi-bin/koha/sco/sco-main.pl
then hit /cgi-bin/koha/opac-main.pl
login
=> 403
Apply this patch, try again
=> Success

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 17:04:59 +00:00
Baptiste Wojtkowski dcbcea80e4
Bug 41602: (follow-up) Add a validation step for filtering
When there is a filter with the "status" param, we add a validation step
to secure the use of such a filter.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:43 +00:00
Jonathan Druart 134fe95444
Bug 41602: Allow _status to be in nested structure
This patch allows _status to be in a nested structure.

Cannot be combined in all the possible ways, but at least it should fix
the UI.

Test plan:
Use the filters on the holdings table and confirm that you can combine
the status with other filters.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:42 +00:00
Jonathan Druart 128c24d58c
Bug 41602: Add a test
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:41 +00:00
Martin Renvoize 36bd8e6635
Bug 42669: Use exception type check for NoNodes and don't mask real failures
Two follow-up fixes from QA review of the previous commit:

- Detect Search::Elasticsearch::Error::NoNodes via ref($_) rather than
  regex-matching the stringified exception, matching the existing
  pattern already used in about.pl.
- Only reset jobs to 'new' for retry when NoNodes is the sole problem.
  If a genuine indexing error also occurred in the same batch, record
  the batch as 'failed' instead of silently discarding that error via
  the NoNodes retry path.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:40 +00:00
Tomás Cohen Arazi e8482427e9
Bug 42669: Fix es_indexer_daemon.pl silent job failure and NoNodes recovery
When update_index() throws, the daemon now:
- On NoNodes: aborts the batch, recreates the indexer object to reset
  the dead-node connection pool, and resets jobs to 'new' for retry.
- On other errors: marks jobs as 'failed' instead of 'finished'.

This prevents silent data loss where records disappear from search
results because their indexing jobs were incorrectly marked as done.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:39 +00:00
Paul Derscheid 852b0f1dcd
Bug 42225: (QA follow-up) Use configured unitprice field in outer SELECT
The outer SELECT hardcoded unitprice_tax_included while the inner
subquery selects the field returned by FieldsForCalculatingFundValues.
With CalculateFundValuesIncludingTax disabled the derived table only
exposes unitprice_tax_excluded, so spent.pl dies with:
Unknown column 'unitprice_tax_included' in 'field list'

To test:
1. Set CalculateFundValuesIncludingTax to 'Exclude'
2. View spent.pl for a fund with received orders
3. Observe error 500
4. Apply patch, restart_all
5. Reload, page renders correctly
6. Set CalculateFundValuesIncludingTax back to 'Include'
7. Reload, page still renders correctly

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:38 +00:00
Nick Clemens 683142868f
Bug 42225: Improve SQL on spent.pl
To test:
1 - Have some received orders and some invoice adjustments
2 - View spent.pl for a fund
3 - Apply patch, restart_all
4 - Reload
5 - Confirm the display has not changed

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:36 +00:00
Jonathan Druart 98ee165b82
Bug 42886: Add missing rtl
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:35 +00:00
Jonathan Druart f87dfcb26e
Bug 42886: Add bookings.css and edifact_interchange.css
Caught by the xt test

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:34 +00:00
Jonathan Druart ec4b88d6f9
Bug 42886: Add xt test to prevent future omission of css files in Makefile
Note that t/Makefile.t and xt/check_makefile.t were doing the same test.
Merging them.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:33 +00:00
David Cook fccc79dd70
Bug 42850: Add ill-requests-rtl.css to Makefile
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:32 +00:00
Andrii Nugged 0c48dd52f0
Bug 42850: Install generated ILL requests CSS
The ILL requests page loads css/ill-requests.css from the staff template. The stylesheet is generated by build-resources.PL and ignored by git, but Makefile.PL does not list it in the generated asset map.

As a result package installs do not copy the generated stylesheet into blib, so Asset.css cannot find it and no link tag is emitted.

Add ill-requests.css to the generated staff CSS file map so it is installed with the other generated staff stylesheets.

Test plan:

1. perl -c Makefile.PL

2. prove -v t/Makefile.t

3. Confirm that /cgi-bin/koha/ill/ill-requests.pl includes a link tag for css/ill-requests.css on a package/source-style install.

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:31 +00:00
Pedro Amorim 43b74cba6e
Bug 42617: (41512 follow-up) Remove ownPagination and fix recordsFiltered propagation
ownPagination set recordsFiltered on json.data instead of json, causing
DataTables to display "NaN total entries". Remove the artificial 5000
mechanism, propagate real counts from plugins that provide them, and
fall back to actual result counts for those that don't. Also removes a
dead drawCallback overwritten before it could fire.

Test plan:
1) Install this test version of the unpaywall plugin:
https://github.com/openfifth/koha-plugin-ill-avail-unpaywall/releases/tag/test
2) Enable ILLModule and ILLCheckAvailability
3) Configure the plugin and add any emaill address
4) Create a new ILL request:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
5) Enter '42' for cardnumber, any library, and any DOI e.g. '123'.
6) Verify that pagination works as expected

Before patch, pagination controls dont show and the following label is shown:
No entries to show (filtered from NaN total entries)

Signed-off-by: Edith Speller <Edith.Speller@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:30 +00:00
Samuel Sowanick 4fd2721f9d
Bug 42519: Terminology - Update 'branch' to 'library' in patron categories
The default message in the "Self-renewal failure message" field
(Administration > Patrons and circulation > Patron categories >
 Account expiry and self-renewal) uses incorrect terminology.

Test plan:
1. Go to Administration > Patrons and circulation > Patron categories.
2. Click on a patron category.
3. Scroll down to the "Self-renewal failure message" field (in the
   Account expiry and self-renewal section).
4. Note the text says "...your local branch...".
5. Apply the patch.
6. Reload the page.
7. Note that the text now says "...your library...".

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:29 +00:00
Jonathan Druart 2b872b182a
Bug 42937: Remove unneeded flag in 00-strict.t
$Test::Strict::TEST_STRICT = 0 is only set for all_perl_files_ok
It's no longer used since bug 42126

Test plan:
prove t/db_dependent/00-strict.t
should still return green

Remove a use Modern::Perl for mainpage.pl
=> Should still return green

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:28 +00:00
Martin Renvoize fe8f079121
Bug 41819: (follow-up) Reject 'AC' refunds for anonymous transactions server-side
The previous patch hides the 'Account credit' option in the refund modal
when the accountline has no borrowernumber, but that is a UI convenience
only. A crafted POST (or a stale page) could still submit
refund_type=AC against an anonymous accountline, which would create a
REFUND credit with no patron to attach it to and leave the register
unbalanced because the payout branch is skipped.

Add a server-side guard in pos/register.pl that refuses the operation
and surfaces a new error_refund_anonymous_ac flag, rendered in
pos/register.tt in the same style as the other refund errors.

Test plan:
1. Apply the patch
2. Log in as a user with cash_management/anonymous_refund permission
3. Record an anonymous sale against a register
4. Using the browser devtools (or curl), POST to pos/register.pl with
   op=cud-refund, refund_type=AC and the accountline id of the sale
5. => Confirm the request is rejected and the
   "Account credit refunds are not available for anonymous transactions"
   message is shown
6. => Confirm no REFUND accountline was created and the register
   balance is unchanged
7. Repeat the POST with refund_type=CASH (or any non-AC type) and
   confirm the refund + payout are created normally

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:27 +00:00
Martin Renvoize b236c67e4a
Bug 41819: POS refunds with 'AC' type incorrectly create PAYOUT accountlines
When processing a refund through the POS register with refund type 'AC'
(Account Credit), the system was incorrectly creating both a REFUND credit
and a PAYOUT debit with payment_type = 'AC'.

When 'AC' is selected, the refund should remain as a credit on the patron's
account without creating a payout. This behavior was already correct in the
member account refund workflow (members/boraccount.pl) but was missing
from the POS refund workflow.

This patch:
1. Adds the same check to pos/register.pl that exists in members/boraccount.pl
   to skip payout creation when refund_type is 'AC', making both workflows
   consistent.

2. Fixes the JavaScript in register.tt to properly hide the 'AC' option for
   anonymous transactions. The original check only tested for empty string,
   but borrowernumber can be undefined/null, which needs to be handled.

Test plan:
Part 1: Test patron transaction refund with Account Credit (AC)
1. Find or create a test patron
2. On the patron's Accounting tab, manually add a charge (e.g., Manual fee)
3. Pay the fee off from the patron's Accounting tab
4. Navigate to Point of Sale > Transaction History
5. Find the payment transaction you just created
6. Click "Issue refund" on the payment line item
7. Verify that "Account credit" appears as an option in the "Transaction type"
   dropdown
8. Select "Account credit" as the transaction type
9. Enter the refund amount and click "Confirm"
10. Navigate back to the patron's Accounting tab
11. Verify that a REFUND credit appears on their account
12. Verify that NO PAYOUT debit was created
13. Verify the credit remains on the patron's account (positive balance)

Part 2: Test patron transaction refund with Cash payout
16. Using the same patron, create another manual fee and payment
17. Navigate to Point of Sale > Transaction History
18. Click "Issue refund" on the new payment
19. Select "Cash" (or another payment type) as the transaction type
20. Enter the refund amount and click "Confirm"
21. Navigate back to the patron's Accounting tab
22. Verify both a REFUND credit and a PAYOUT debit appear
23. Verify the PAYOUT has the correct payment_type (e.g., CASH)

Part 3: Test anonymous transaction (no Account Credit option)
24. Navigate to Point of Sale
25. Add an item to sell and complete the payment
27. Navigate to Point of Sale > Transaction History
28. Find the anonymous payment you just created
29. Click "Issue refund" on the payment line item
30. Verify that "Account credit" does NOT appear in the "Transaction type"
   dropdown (only payment types like Cash, Check, etc. should appear)
31. This confirms anonymous transactions cannot have account credits since
    there's no patron account to credit

Expected results:
- Patron transactions show "Account credit" option in refund dropdown
- Anonymous transactions do NOT show "Account credit" option
- AC refunds create only REFUND credit (no PAYOUT debit)
- Non-AC refunds create both REFUND credit and PAYOUT debit
- Run: prove t/db_dependent/Koha/Cash/Register/Cashup.t

Signed-off-by: Laura Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:26 +00:00
Jason Robb e110ef135c
Bug 43046: Checkbox labels in Currencies and EDI accounts have unnecessary colons
Removes unnecessary colons when adding currencies and edi accounts.

1. Enable EDIFACT syspref
2. Navigate to Admin > Currencies.. > New currency
3. Note unnecessary colons after checkbox labels
4. Navigate to Admin > EDI accounts > New account
5. Note unnecessary colons after checkbox labels
6. Apply patch
7. Repeat steps 2 and 4, note colons have been removed

Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:25 +00:00
Baptiste Wojtkowski b5fc521017
Bug 39208: Remove xhtml2pdf from printoverdues.sh
Note: this requires a command line access to the test machine

TEST PLAN:
1 - Anywhere on the computer running koha, run following command:
mkdir tmp
echo '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><p>Тетрис — это классика!</p></body></html>' > tmp/tmp.html
echo '* { color: red };' > tmp.css
./misc/cronjobs/printoverdues.sh tmp tmp.css
2 - Open the pdf in tmp/tmp.pdf, notice it is broken
3 - Apply patch
4 - Rerun ./misc/cronjobs/printoverdues.sh tmp tmp.css
5 - Open the pdf in tmp/tmp.pdf, notice it is now displaying properly, notice the css property still applies

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:24 +00:00
Phil Ringnalda b1b12578e0
Bug 42920: Looking up authority records in Advanced editor shouldn't append spaces
The patch for bug 21453 replaced an Advanced editor-specific (in the "[% IF rancor %]" block) "[%- END -%]‡9[% authid | html %]";" which trims whitespace with several lines of  "[% ... %]" which do not trim whitespace. The result is that when you look up an authority from the Advanced editor, you get extra whitespace between the last subfield from the authority record and the $9 field with the authority id.

Test plan:
 1. Without the patch, set the preference EnableAdvancedCatalogingEditor to Enable
 2. Cataloging - Advanced editor
 3. Click in any line of the editor and press Return to get a blank line
 4. Type 655 and press Tab three times
 5. Type Ctrl+Shift+L to open the authority search popup
 6. Click Search (no need for search terms) and click Choose for any record
 7. Note that the editor shows twenty blank spaces between the end of the subfield a
    contents and the ‡9
 8. Apply patch
 9. Repeat steps 3-6, note that now there is no extra space between the end of the
    content and the ‡9

Sponsored-by: Chetco Community Public Library
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:23 +00:00
Martin Renvoize 04381f6aba
Bug 41358: Apply consistent JSON logging to holds (Bug 40866)
Bug 40866 introduced JSON logging for HOLDS CREATE but stored null for
'confirmations' and 'forced' when no overrides were present. This patch
mirrors the earlier change to CIRCULATION ISSUE by defaulting them to
empty arrays, keeping the two log streams structurally identical and
simplifying reporting queries.

Changes:

C4/Reserves.pm:
- Default confirmations/forced to [] in the AddReserve log payload.

t/db_dependent/Reserves.t:
- Update the Bug 40866 subtest to assert the empty-array defaults on
  normal holds, and fix a 'my $log_data' redeclaration.

tools/viewlog.pl:
- Extend the CSV export JSON-decode block to also cover HOLDS logs,
  guarding the item lookup on itemnumber being present (HOLDS payloads
  may lack one) and filling the CSV item columns from the decoded
  payload. The legacy bare-itemnumber fallback is restricted to
  CIRCULATION so a legacy HOLDS hold_id is not mistaken for an
  itemnumber.

koha-tmpl/intranet-tmpl/prog/js/viewlog.js:
- Extend the info column override rendering (renderCircInfo) to HOLDS
  entries so forced/confirmations arrays display translated, exactly as
  they do for CIRCULATION. The log viewer moved to an ajax datatable in
  bug 29016, so this replaces the earlier json_found change to
  viewlog.pl/viewlog.tt.

misc/maintenance/migrate_action_logs_to_json.pl:
- Extended to handle legacy pre-Bug 40866 HOLDS CREATE logs where info
  is a bare hold_id, recovering branchcode / biblionumber / itemnumber
  from old_reserves / reserves.
- Uses the same keyset-pagination pattern as the circulation loop so
  rows are not skipped under --commit.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:22 +00:00
Martin Renvoize 507545734e
Bug 41358: Log forced overrides for offline and ILL checkouts
This patch updates offline circulation and ILL checkouts to properly
record their nature in circulation logs as forced transactions.

Offline circulation (process_koc.pl):
- Offline transactions have already occurred without access to normal
  circulation checks
- Now marks these as forced with 'OFFLINE_CIRCULATION' override code
- This helps identify historical transactions that bypassed checks

ILL checkouts (Koha/ILL/Request.pm):
- ILL checkouts are system-initiated and may bypass certain restrictions
  by design
- Now marks these as forced with 'ILL_CHECKOUT' override code
- This helps track and report on ILL circulation activity separately

Display updates (action-logs.inc):
- Added translation strings for both new override codes:
  - OFFLINE_CIRCULATION: "Offline circulation checkout"
  - ILL_CHECKOUT: "ILL system checkout"
- These will now display properly in Tools > Log viewer

This ensures these special checkout types are properly identified in
reporting and analysis, making it easier to understand the context
of historical transactions.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:21 +00:00
Martin Renvoize c64aea8fcc
Bug 41358: Add migration script for legacy circulation logs
This patch adds an out-of-band migration script to convert legacy
CIRCULATION ISSUE action logs from the old itemnumber-only format
to the new consistent JSON format.

The script:
- Finds all CIRCULATION ISSUE logs where info is just an itemnumber
- Converts them to JSON format with the standard structure
- Attempts to recover issue_id and branchcode from old_issues/issues
- Runs in dry-run mode by default (--commit flag to actually update)
- Processes in batches to handle large databases safely
- Includes verbose mode for detailed output

Usage:
  perl misc/maintenance/migrate_action_logs_to_json.pl --help
  perl misc/maintenance/migrate_action_logs_to_json.pl --verbose
  perl misc/maintenance/migrate_action_logs_to_json.pl --commit

This is designed as an out-of-band maintenance script rather than
an atomic update because:
1. May be expensive on large databases with many log entries
2. Migration is optional - display code handles both formats
3. Can be run during off-peak hours at admin's discretion

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:20 +00:00
Martin Renvoize 8a9fb57a93
Bug 41358: Always store circulation logs in consistent JSON format
Previously, CIRCULATION ISSUE logs would store just the itemnumber when
there were no confirmations or forced overrides, but would store a full
JSON structure when overrides were present. This inconsistency makes
reporting difficult as queries need to handle both formats.

This patch changes AddIssue to always store logs in JSON format,
matching the pattern already used in AddRenewal. The JSON structure
now consistently includes:
- issue: issue_id
- branchcode: branchcode
- itemnumber: itemnumber
- confirmations: array of confirmation codes (empty array if none)
- forced: array of forced override codes (empty array if none)

Benefits:
- Consistent data format simplifies reporting and analysis
- Matches existing RENEWAL logging pattern
- Display code (tools/viewlog.pl) already handles both formats,
  so backward compatibility is maintained

Test plan:
1. prove t/db_dependent/Circulation.t
2. Perform a normal checkout - verify action log contains JSON
3. Perform a checkout with overrides - verify confirmations/forced
   are populated in the JSON
4. Check Tools > Log viewer - both types should display correctly

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:19 +00:00
Nick Clemens 2e4b527782
Bug 42512: Pass already checked/cleaned MARC::Record to Metadata Extractor
To test:
1 - In Koha testing docker record 369 has invalid xml, otherwise corrupt a record like:
    sudo koha-mysql kohadev
    UPDATE biblio_metadata SET metadata = UpdateXML(metadata, '//datafield[@tag="040"]/subfield[@code="c"]',CONCAT('<subfield code',CHAR(27),'="c">OSt</subfield>')) WHERE
 biblionumber=438;
2 - View the record in the staff client - note bad metadata message
    http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=438
3 - Click 'Edit'
4 - Error 500, in logs:
    Invalid data, cannot decode metadata object
5 - Apply patch, restart all
6 - Reload record and click edit
7 - Success! Message about bad metadata, but can edit/save

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:18 +00:00
Jan Kissig c0c0b6bbfe
Bug 42986: Fix Uncaught TypeError on paycollect
This patch introduces a check if the variable change is defined, before its property innerHTML is accessed.
When calling "Write off selected" an Uncaught TypeError is thrown on form submission when accessing change.innerHTML as the variable change is undefined.
This regression was introduced by bug 42466 in a new submit handler which did not check if 'change' was defined at all.

Test plan:
1. In the staff interface, go to a patron's account
2. Go to Accounting > Create manual invoice
3. Fill out the manual invoice form with an amount
4. Click Save
5. Go to Make a payment
6. Click "Write off selected" (not write off on a single row)
7. IMPORTANT : Open the browser console
8. Click Confirm
9. apply patch and redo steps 1 - 8.

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:17 +00:00
Owen Leonard abdfe256ec
Bug 42568: Match maxlength attributes to marc_order_accounts column sizes
This patch adds a maxlength attribute to a few form fields in the MARC
order accounts entry form. Text inputs should now allow the user the
enter more characters than the db will accept.

To test, apply the patch and enable the MarcOrderingAutomation system
preference if necessary.

- Go to Administration -> MARC order accounts -> New account.
- Test these inputs to confirm that you can't enter more characters than
  their maxlengths:
  - Description (250)
  - Match field (10)
  - Match value (50)
  - Basket name field (10)

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:16 +00:00
Lucas Gass c5b39eb684
Bug 41604: (QA follow-up) Remove unused JS variable
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:15 +00:00
Lucas Gass d74dea01ac
Bug 41604: (QA follow-up) Add a semicolon
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:14 +00:00
Jonathan Druart 1abfc554f0
Bug 41604: Correctly restore alert when bKohaColumnsUseNames is off
If we define visibility_condition we must have implemented
bKohaColumnsUseNames correctly.

Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:13 +00:00
Jonathan Druart 26d0124c6b
Bug 41604: Hide column if the feature is disabled
Example: supplier_report on the basket page
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:12 +00:00
Jonathan Druart 2a34413601
Bug 41604: Save user decision per column when visibility changes
- Modify column-visibility.dt event handler to store the visibility
  preference of the user
- Improve _dt_force_visibility() to correctly pick the correctly visibility value depending
  on visibility_condition, is_hidden, and the user preference
- Add _dt_get_expected_column_visibility() to guess what's the expected visibility
- Force boolean true/false (vs 1/0) using !!
- Rely on a global variable kohaTableUserChoices to store the user
  choice. It will be part of the state and store in localStorage.

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:10 +00:00
Jonathan Druart 85ccb3d4d7
Bug 41604: Add one more Cypress test
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:09 +00:00
Jonathan Druart 4dab120d32
Bug 41604: Add Cypress tests
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:08 +00:00
Lucas Gass d20f7bc7a7
Bug 41604: (follow-up) Fix the visibility when visibility_condition is 0 or null, instead of false
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:07 +00:00
Jonathan Druart f1160e7a70
Bug 41604: Fix force display of a column
If a column must be displayed but was hidden before

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:06 +00:00
Jonathan Druart 6e5ee1fdf7
Bug 41604: Fix typo
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:05 +00:00
Jonathan Druart 060c8918d2
Bug 41604: QA fixes
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:04 +00:00
Jonathan Druart 3efad3f74a
Bug 41604: take visibility_condition when save state is disabled
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:03 +00:00
Jonathan Druart 4d2f963f1c
Bug 41604: Use visibility_condition in other places
There are 3 other places where we force the visibility of columns. They
must als use this new visibility_condition flag, so they are all using
it.

Test plan:
The 3 tables are:
1. basket with the columns edifact, modify, cancel
2. hold history with the column itemtype
3. batch edit patron with the columns checkbox, password expiration,
   protected

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:02 +00:00
Jonathan Druart 7c688774f1
Bug 41604: Centralize visibility condition in datatables.js
We want to hide column if:
 * the feature is not accessible (pref disabled or insufficient
   permissions)
or
 * the column is hidden in the settings
or
 * the column has been hidden by the user

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:54:01 +00:00
Hammat Wele 306ebc18ea
Bug 41604: Impossible to hide Checkin column in issues-table in circ/circulation.pl
Bug 41314 caused a regression, making it impossible to hide the Checkin column in the issues table in the patron's circulation page.

To recreate:
1. Hide the checkin column by default in circulation issues-table
   1.1. Go to Administration > Table settings > Circulation tab > Jump to page : circulation > Table id: issues-table
   1.2. Check 'Is hidden by default' for 'checkin'
   1.3. Click 'Save'
2. logout and login
3. Checkout an item to a patron
   3.1. In the search bar, set to Checkout, search for Henry
   3.2. In the checkout field, enter the barcode 39999000010114 and press enter
   --> In the table of issues, Checkin column is visible, it should be hidden by default
4. Apply the patch
5. Repeat step 2, 3
   --> In the table of issues, Checkin column is not visible

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-16 10:53:57 +00:00
Lari Taskula fe312b32d1
Bug 41796: "Forgot your password" link is not visible if OpacResetPassword is enabled but OpacPasswordChange is disabled
Before applying this patch, to reproduce the issue:
1. Set system preference OpacResetPassword to 'not allowed'
2. Set system preference OpacPasswordChange to 'Don't allow'
3. Set preference opacuserlogin to 'Allow'
4. Navigate to OPAC
5. Observe no "Forgot your password?" link on the OPAC main page
6. Set system preference OpacResetPassword to 'allowed'
7. Observe no "Forgot your password?" link on the OPAC main page
8. Set system preference OpacPasswordChange 'Allow'
9. Observe "Forgot your password?" link is present on the OPAC main page.

Instead, it should be already present when OpacResetPassword is allowed.

To test this patch:
1. Apply this patch
2. Set system preference OpacResetPassword to 'not allowed'
3. Set system preference OpacPasswordChange to 'Don't allow'
4. Set preference opacuserlogin to 'Allow'
5. Navigate to OPAC
6. Observe no "Forgot your password?" link on the OPAC main page
7. Set system preference OpacResetPassword to 'allowed'
8. Observe "Forgot your password?" link is present

Signed-off-by: Bo Gustavsson <bosse@gustavsson.one>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:41 +00:00
Laura_Escamilla 787eb80e9d
Bug 42339: Make Record display customization cancel return to record display list
1. Go to the staff interface.
2. Go to Tools > Additional contents.
3. Select the Record display customizations tab/category.
4. Add a new Record display customization, or edit an existing one.
5. Click Cancel.
6. Confirm you are returned to:
   /cgi-bin/koha/tools/additional-contents.pl?category=record_display
7. Confirm you are not returned to the HTML customizations category.
8. Repeat with another Additional content category, such as Pages, and
   confirm its Cancel behavior is unchanged.
9. Sign off and have a great day! :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:40 +00:00
Jonathan Druart 1ed63c079d
Bug 42735: Fix SQL injection
This change fixes an SQL injection in the circulation stats.

Test plan:
0. Apply the patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/issues_stats.pl
3. Try out a bunch of options
4. Try to break it
5. Note that you just get output and no errors

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:38 +00:00
Jonathan Druart 9e39e68a98
Bug 42369: Use C4::Context::csv_delimiter for delimiter validation
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:37 +00:00
David Cook 310201b520
Bug 42369: Fix SQL injection
This change fixes SQL injection in bor issues top

Test plan:
0. Apply patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/bor_issues_top.pl
3. Try out all the different options
4. Try to break it
5. Note that you get output and no errors

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:36 +00:00
Jonathan Druart 44c7c10a19
Bug 42368: Use C4::Context::csv_delimiter for delimiter validation
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:35 +00:00
David Cook 0af6fbb7e7
Bug 42368: Fix SQL injection in issue avg stats
This change fixes SQL injection in issues avg stats

Test plan:
0. Apply the patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/issues_avg_stats.pl
3. Try out all the different options
4. Try to break the report
5. Note that you always get output instead of errors

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:34 +00:00
Jonathan Druart 70f6545a53
Bug 42363: Use C4::Context::csv_delimiter for delimiter validation
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:33 +00:00
David Cook e7f36c5b87
Bug 42363: Fix SQL injection
This change cixes an SQL injection in cat stats

Test plan:
0. Apply patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/catalogue_stats.pl
3. Try out different values
4. Try to break the report
5. Note that you just get output and not errors

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:32 +00:00
Jonathan Druart 5827f2f774
Bug 42360: Move separator validation to C4::Context::csv_delimiter
So we protect other calls

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:31 +00:00
David Cook 1d9c8fd390
Bug 42360: Also validate sep param
Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:30 +00:00
David Cook 033acbe94e
Bug 42360: Fix SQL injection
This change fixes an SQL injection in the acq stats.

Test plan:
0. Apply the patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/acquisitions_stats.pl
3. Try out a bunch of options
4. Try to break it
5. Note that you just get output and no errors
NOTE: In a fresh KTD you likely don't have any actual statistics

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:28 +00:00
Martin Renvoize 7a10a59e3d
Bug 43033: Sort unallocated task indices before compacting the retry matrix
The previous two commits on this bug fixed two ordering assumptions in
the RETRY loop of _allocateWithTransportCostMatrix(): resetting $inf at
the start of each attempt, and splicing unallocated task indices out of
@requests in descending order (removing a lower index first would shift
the position of any higher index still waiting to be removed).

This is a third instance of the same underlying problem, a few lines
earlier in the loop that compacts the working cost matrix $m before
that splice runs. @unallocated is built by iterating over agents (item
rows), pushing the column index Munkres assigned to each row that
turned out infeasible - and Munkres' row-to-column assignment is a
permutation, not a monotonic function of the row index, so
@unallocated is no more reliably sorted here than it was for the
splice.

That compaction loop scans matrix columns in ascending order and
advances a single pointer into @unallocated, expecting to meet each
entry in that same ascending order. When @unallocated arrives out of
order, the pointer never catches up: columns that should have been
dropped are left in place, and the columns after them are never
shifted left to fill the gap. From that point on, $m no longer lines
up with @requests, which *is* correctly reduced by the splice - so on
the next retry pass a hold's transport cost gets evaluated using a
different hold's matrix row. Depending on the exact misalignment, this
can silently drop a fillable hold from the queue while a genuinely
available item sits unused, or fill a hold via the wrong-cost item.

It didn't show up in the "many holds" regression test added earlier on
this bug, because that test's transport cost matrix is uniform (every
disabled pair costs the same); this defect only surfaces once the
columns competing for a scarce item have distinct costs.

Sort @unallocated once, ascending, and use that copy for the
compaction pass; reverse it for the splice, which needs descending
order.

Test plan:

1. Run the new t/db_dependent/HoldsQueue/RetryAllocationOrdering.t -
   it calls _allocateWithTransportCostMatrix() directly with a small
   hand-built scarce transport cost matrix and a pinned srand() (the
   specific item-to-branch permutation needed to trigger the bug
   depends on the shuffle() inside the routine). Reverting just this
   patch makes 3 of its 6 subtests fail: the L2 pickup hold is dropped
   even though an available item could have filled it.
2. Run t/db_dependent/HoldsQueue/TransportCostOptimizations.t to
   confirm no regression in the existing retry-loop coverage.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:27 +00:00
Andreas Jonsson f68c481391
Bug 43033: retry allocation with valid items
* The representation of the value "infinity" must be reset to -1 at the
  start of each allocation attempt.
* Unallocated items must be spliced out from the candidate arrays in
  reverse index order.

Test plan:

1. Run the unit tests in
   t/db_dependent/HoldsQueue/TransportCostOptimizations.t

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:26 +00:00
Nick Clemens f94cfcfee4
Bug 43033: Test with a lot of holds
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:24 +00:00
Martin Renvoize 611328b5db
Bug 42430: (QA follow-up) Fix nested plugin package corruption on reload
InstallPlugins forces every discovered plugin class to be deleted and
re-required from disk on every call, so plugin_methods reflects the
.pm file as it currently exists rather than whatever was already
loaded into a long-lived Plack worker. That deletion used
Symbol::delete_package, which clears the target package's whole
symbol table -- including any already-loaded packages nested under it
(e.g. a REST API controller a plugin bundles alongside itself, such as
Koha/Plugin/Foo/Controller.pm). Those nested classes keep their own
%INC entry, so they are never re-required afterwards and are left
permanently blanked out for the rest of the worker's life.

This was caught when running
`prove -r t/db_dependent/Koha/Plugins/ t/db_dependent/Koha/REST/Plugin/`
on top of the original patch. Tests fail several files that have nothing
to do with the bug being fixed (Biblio_and_Items_plugin_hooks.t,
Holds_hooks.t, TemplateIncludePathHook.t, REST/Plugin/PluginRoutes.t),
because Koha::Plugin::Test::Controller (a bundled test-fixture
controller living in t/lib/plugins/Koha/Plugin/Test/Controller.pm)
gets its symbol table wiped as collateral damage every time
Koha::Plugin::Test is reloaded.

_forget_plugin_class() replaces the delete_package call with the same
technique used by the CPAN module Class::Unload (not added as a
dependency here, just the technique): only clear the target class's
own symbols and reset its @ISA, explicitly leaving any nested package
entries in its symbol table alone. Module::Pluggable discovers bundled
nested classes as their own entries in the plugin list regardless, so
they still get reloaded correctly on their own turn.

I also annotate the no strict 'refs' block added by the previous commit
with a '## no critic' justification, as flagged by koha-qa.pl.

Test plan:
1. Confirm the bug exists without this patch:
   a) git checkout <previous commit> (drop this commit, keeping the
      first 3 commits of the bug applied)
   b) Inside KTD:
      prove -r t/db_dependent/Koha/Plugins/ t/db_dependent/Koha/REST/Plugin/
   c) Observe failing files unrelated to this bug's own tests:
      Biblio_and_Items_plugin_hooks.t, Holds_hooks.t,
      TemplateIncludePathHook.t and REST/Plugin/PluginRoutes.t.
2. Confirm the fix:
   a) Re-apply this commit
   b) Re-run the same command from 1(b) -- all tests now pass
3. Confirm the original bug this branch set out to fix is still fixed,
   both via the manual UI steps from the first commit's test plan
   (install Batch Permission Modifier Plugin 1.3.1, upload 1.4.1 over
   it via Tools > Plugins, confirm the renamed method takes effect
   without restarting Plack) and via:
   prove t/db_dependent/Koha/Plugins/Plugins.t
   (see the 'InstallPlugins() reloads stale symbol tables tests'
   subtest)
4. Confirm QA tooling is clean:
   koha-qa.pl -c 4
   => PASS for Koha/Plugins.pm and all touched test files, no critic
      or tidiness failures.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:23 +00:00
Nick Clemens c1c733fc08
Bug 42430: (follow-up) Add missing POD
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:22 +00:00
Kyle M Hall fff6a7574c
Bug 42430: Add unit tests
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:21 +00:00
Kyle M Hall ef26c1acaa
Bug 42430: Fix issue with stale plugin methods after plugin upgrade
When a plugin is upgraded in place (by uploading a new version over
the old one), the list of methods Koha knows about for that plugin
can get out of date. For example, if a plugin used to have a "tool"
method and the new version renames it to "admin", the staff interface
can still show the old "Run tool" link, and clicking it produces an
error saying the plugin has no "tool" method.

This happens because the plugin upload runs inside a long-lived
Plack worker that has already loaded the old version of the plugin
in memory. When Koha scans the plugin to record its methods, Perl
has no reason to re-read the file from disk, so the scan sees the
old method list and stores that in the plugin_methods table. The
plugin code on disk is new, but the record of what methods it has
is stale.

This patch forces Koha to throw away the in-memory copy of each
plugin and reload it from disk before scanning, so the recorded
list of methods matches the plugin that is actually installed.

Test Plan:
1) Install the Batch Permission Modifier Plugin version 1.3.1
(this version exposes a "tool" method)
2) Go to Tools > Plugins and confirm the plugin row shows
"Run tool" in its actions menu
3) Upload Batch Permission Modifier Plugin version 1.4.1 over
the top of 1.3.1 (this version exposes "admin" instead of
"tool")
4) Reload the Plugins page
5) Note the plugin still shows "Run tool" in its actions menu
6) Click "Run tool"
7) Note the error: the plugin has no "tool" method!
8) Apply this patch
9) Restart all the things!
10) Uninstall the plugin
11) Repeat steps 1-4
12) Note that "Run tool" is gone and "Run admin tool" appears
in its place
13) Click "Run admin tool" and confirm it loads without error!

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:20 +00:00
Martin Renvoize 6563255743
Bug 42995: (QA follow-up) Harden nested +count FK-chain resolution
The FK-chain resolution added in the previous patch only handles ONE
level of nesting: it requires $parent_alias to be a direct relationship
of $main_source. This holds for every +count embed currently declared
in api/v1/swagger/ (all exactly one level deep, e.g.
biblio.items+count), but if that assumption is ever violated - a
deeper nested +count, a parent relation with a coderef (custom join)
condition, or no matching FK column name - the previous code silently
fell back to the pre-fix expression ($parent_alias.$fk_self), which can
reference a join alias that isn't available inside DBIC's limiting
subquery, reproducing the original 500 with no diagnostic.

This patch makes _build_count_subquery decline to build a subquery
(return undef) when the correlation can't be safely resolved, instead
of emitting SQL that may be broken. The caller already has an existing,
tested degradation path for "no DBIC relationship info" (prefetch the
association directly; to_api falls back to the Perl-level ->count call;
sorting on it is rejected with a clean 400) - unresolved nested
correlations now go through that same path rather than risking a 500.

Also:
- Extract the repeated "_resultset vs _result" result-source lookup
  into a shared _result_source() helper (was duplicated in
  dbic_merge_prefetch and _build_count_subquery).
- Document the one-level-deep limitation in _build_count_subquery's POD.
- Add a regression test pinning the 2-level-nested-+count case: it must
  fall back to a plain prefetch (no COUNT subquery) rather than error
  or emit unsafe SQL.

Test plan:
1. ktd --shell
2. prove t/Koha/REST/Plugin/Query.t t/db_dependent/Koha/Object.t \
       t/db_dependent/Koha/REST/Plugin/Objects.t \
       t/db_dependent/api/v1/acquisitions_orders.t \
       t/db_dependent/api/v1/acquisitions_vendors.t \
       t/db_dependent/api/v1/ill_requests.t \
       t/db_dependent/api/v1/tickets.t \
       t/db_dependent/api/v1/patrons.t
=> All pass, no regressions.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:18 +00:00
Tomás Cohen Arazi f431fe0da0
Bug 42995: Fix nested +count subqueries with pagination and has_many
When nested +count embeds (e.g. biblio.items+count) are used alongside a
has_many prefetch (e.g. biblio.suggestions or the implicitly added
biblio.biblioitem) and pagination, DBIC's limiting subquery optimization
places +select scalar refs in the inner subquery where the parent join
alias (e.g. 'biblio') is not yet available.

This patch fixes _build_count_subquery to resolve the correlation back to
the main table (me) via the FK chain. Instead of generating:

  (SELECT COUNT(*) FROM items
   WHERE items.biblionumber = biblio.biblionumber)

It now generates:

  (SELECT COUNT(*) FROM items
   WHERE items.biblionumber = me.biblionumber)

This works because me.biblionumber IS the FK to biblio, holding the same
value. The main table columns are always available regardless of DBIC's
query structure.

Changes:
- Add $main_source parameter to _build_count_subquery for FK resolution
- Thread main_source through _parse_prefetch and dbic_merge_prefetch
- When parent_alias != 'me', resolve through parent relationship condition
- Update Query.t assertion to match the new correlation expression

Test plan:
1. Apply the regression tests from the previous commit
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/api/v1/acquisitions_orders.t
=> FAIL: The Bug 42995 subtest fails with 500 errors
3. Apply this patch, repeat from step 2
   k$ prove t/Koha/REST/Plugin/Query.t \
       t/db_dependent/Koha/Object.t \
       t/db_dependent/Koha/REST/Plugin/Objects.t \
       t/db_dependent/api/v1/acquisitions_orders.t \
       t/db_dependent/api/v1/acquisitions_vendors.t \
       t/db_dependent/api/v1/ill_requests.t \
       t/db_dependent/api/v1/tickets.t \
       t/db_dependent/api/v1/patrons.t
=> SUCCESS: Tests pass!
4. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:17 +00:00
Tomás Cohen Arazi e560b85c6f
Bug 42995: (regression tests) Nested +count embeds with has_many prefetch
This patch adds regression tests for the 500 Internal Server Error
triggered when GET /api/v1/acquisitions/orders is called with nested
+count embeds (e.g. biblio.items+count) combined with a has_many
prefetch (e.g. biblio.suggestions.suggester) and pagination.

The root cause is that DBIC's limiting subquery optimization places
+select scalar refs in the inner subquery where the parent join alias
(biblio) is not yet available, resulting in:
  DBD::mysql: Unknown column 'biblio.biblionumber' in 'WHERE'

The regression was introduced in:
  commit e51334604f
  Bug 41950: Make +count embeds sortable via SQL COUNT subqueries

Test plan:
1. Apply this patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/api/v1/acquisitions_orders.t
=> FAIL: The new subtest fails with 500 errors instead of 200
3. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-13 15:46:13 +00:00
Owen Leonard b0fa3d7e6a
Bug 42618: Incorrect sidebar menu link to MARC order accounts
The administration sidebar menu has an incorrect link for MARC ordering
accounts. Clicking the link takes you to EDI accounts. This patch fixes
it.

To test, apply the patch and run 'yarn build'

- Go to Administration -> Libraries (for example)
- In the sidebar menu, click the MARC ordering accounts link.
- You should be taken to the MARC ordering accounts page.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-01 09:34:47 +00:00
Caroline Cyr La Rose 2b2b4b74ba
Bug 42885: FineNoRenewals description mentions fines rather than charges
This patch fixes the terminology in the FineNoRenewals system preference
description to use the term 'charges' instead of 'fines'.

To test:

1. Apply patch
2. Go to Administration > System preferences
3. Search for FineNoRenewals
4. Make sure the description is gramatically correct and doesn't have
   contain typos

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-01 09:34:46 +00:00
Chris Mathevet 4003a4aa90
Bug 42579: Added "no-export" class to ratings div
"Title" column of Checkout history export contains too much
informations, making the export file hard to read and understand.

Test plan :
1 - Connect on OPAC
2 - Go to "Your account"
3 - Go to "Checkout history"
4 - Click on "CSV"
5 - Open file
6 - Notice that the "title" column is too long and contains ratings
    informations
7 - Go back on OPAC
8 - Click on "Copy"
9 - Copy to a .txt file
10 - Notice the same problem as the CSV export
11 - Apply patch
12 - Repeat previous instructions
13 - Notice that the "title" column contains only the title

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-01 09:34:45 +00:00
Kyle M Hall 839f3492bc
Bug 42571: Sending EDI order results in variable not available warnings
The warnings look like:

[2026/05/08 15:02:42] [32918] [WARN] Variable "@messages" is not available at /usr/share/koha/intranet/cgi-bin/acqui/basket.pl line 698.
[2026/05/08 15:02:42] [32918] [WARN] Variable "$op" is not available at /usr/share/koha/intranet/cgi-bin/acqui/basket.pl line 704.

Note in the fix, we are switching to the "our" keyword. When we declare
things with "our", and we run this under plack, those variables and
values persist across multiple requests! Due to this behavior we always
need to reinitialize anything that uses "our" unless we specifically
want that behavior ( like caching ).

Test plan:
1) Send an EDI order, note the warnings in the logs
2) Apply this patch
3) Restart all the things!
4) Send another EDI order, note the warnings are gone!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-01 09:34:44 +00:00
Jonathan Druart 2472e423cb
Bug 42551: Fix QA failures
[FAIL] t/db_dependent/Koha/Plugins/C3.t
   FAIL   file_permissions
                File must have the exec flag
   FAIL   test_no_warnings
                'use Test::NoWarnings' must be present in this test file.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-01 09:34:42 +00:00
Kyle M Hall c79b0b9ef8
Bug 42551: Skip preload when running perl with -c
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-01 09:34:41 +00:00
Kyle M Hall e48c124ac3
Bug 42551: Add unit test
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-01 09:34:40 +00:00
Lisette Scheer 9be8db9c8a
Bug 41889: Update API to allow for null patron_id to be returned when listing checkouts
If a patron is deleted and there's a checkout associated with them, an api call to {url}/api/v1/checkouts?checked_in=true
will result in an error. This patch updates the api to allow for a null value to be returned for patron_id

To test:
Before applying the patch:

1. In Administration->System preference enable the RESTBasicAuth system preference.
2. Checkout an item to a patron (I used item barcode: 39999000000252 and patron barcode 23529000121682)
3. Check the item back in
4. In your brower go to {sandbox/ktdurl}/api/v1/checkouts?checked_in=1
5. Observe there is a patron ID in the JSON response
6. Delete the patron
7. Repeat step 4
8. Observe an error because a "null" value was returned
9. Apply patch
   Sandbox:
       In sandbox management Actions->Apply patches
       Once complete Actions->Build CSS and JS
       Actions->Restart services
   KTD:
       ktd --shell
       git bz apply 41889
       yarn build
       restart_all

10. Repeat step 4
11. Observe the checkout loads, but now has a null patron id.
12. Sign off
On KTD can also run the script: qa
to run tests.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-01 09:34:39 +00:00
Jonathan Druart cb47e6fad7
Bug 42278: Fix JS error
Uncaught SyntaxError: redeclaration of non-configurable global property ClaimReturnedLostValue

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-07-01 09:34:35 +00:00
Jonathan Druart dd918e0de1
Bug 42522: Add link to biblio from eholding show view
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:19 +00:00
Jonathan Druart 4844c93a23
Bug 42290: Tidy all script tags - suggestion (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:17 +00:00
Jonathan Druart e4293f93d2
Bug 42290: Tidy all script tags - suggestion
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the suggestion module.

Trivial changes, the main changes have been done already, we only move
them to a separate script tag.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:16 +00:00
Jan Kissig 36e00f5aff
Bug 42283: (QA follow-up) Change icons
Changes the icon on concerns resolved filter to suit other filters set via AV TICKET_STATUS.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:14 +00:00
Paul Derscheid a4ec38d5fa
Bug 42283: (follow-up) URI-encode z3950 search params in GetZ3950Terms
The original template rendered each Z39.50 search param with the
"| uri" TT filter, so the generated query string was safe for
values containing spaces, ampersands or other reserved characters.

The JS port concatenates the raw values, producing a broken query
string when any search field (title, author, subject, etc.) is not
already URL-safe.

Wrap name and value in encodeURIComponent to restore the original
behaviour.

To test:
- Import or create a biblio whose title or author includes a space
  and an ampersand (e.g. "Q & A: Practical tests").
- From the cataloging search results, trigger the Z39.50 search for
  that record.
- Confirm the request URL includes correctly encoded parameters
  (space as %20, & as %26) and that the Z39.50 results load.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:13 +00:00
Paul Derscheid c7422c5c78
Bug 42283: (follow-up) Fix checkbox selector in concerns status filter
Porting the per-status click handler out of the TT loop dropped the
"hide_" prefix on the checkbox selectors:

- the `is(":checked")` test looked at `#<av>` instead of
  `#hide_<av>`, so the branch that removes the filter never fired
- the toggle line read `!$("#hide${av}")` (no underscore) before
  writing `#hide_<av>`, so the checkbox state was always forced to
  true and further clicks stopped toggling

Restore `#hide_${av}` in both places so the filter toggles match the
original TT-rendered selectors (IDs `hide_<av>` on the hidden
checkboxes).

To test:
- Set up TICKET_STATUS authorised values and enable CatalogConcerns
  and OpacCatalogConcerns.
- Create concerns in multiple statuses, open
  /cgi-bin/koha/cataloguing/concerns.pl.
- Click a "Filter <status>" link: rows with that status disappear.
- Click the same link again: it flips back to "Include <status>"
  and rows reappear. Repeat across several statuses.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:12 +00:00
Jonathan Druart 2cf1f980d0
Bug 42283: Tidy all script tags - cataloguing (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:11 +00:00
Jonathan Druart 0aeb26417e
Bug 42283: Tidy all script tags - cataloguing
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the cataloguing module.

The 3 main changes are in the following files:
* addbooks.tt
* additem.tt
* concerns.tt

There are non-trivial changes in concerns.tt. You need to create several
authorised values for TICKET_STATUS, turn on CatalogConcerns and OpacCatalogConcerns, create several concerns and go to /cgi-bin/koha/cataloguing/concerns.pl
Confirm that the filters are working correctly

Note that cataloguing/addbiblio.tt is on a separate bug (bug 42284)

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:10 +00:00
Jan Kissig 5988883898
Bug 42282: (QA follow-up) Split JS to TT block in results, auto tidy
Splits JS to TT and non TT block. Auto tidy

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:09 +00:00
Jan Kissig c9bfc66026
Bug 42282: (QA follow-up) Split JS to TT block in advsearch, auto tidy
Splits JS to TT and non TT block. Auto tidy

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:08 +00:00
Jan Kissig a0c2e80678
Bug 42282: (QA follow-up) restore searchid in stockrotation
This patch sets the TT param searchid to enable browsing though search results on stockrotation.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:07 +00:00
Jan Kissig 3bf91a73dc
Bug 42282: (QA follow-up): Fix call of HTML5MediaEnabled and HTML5MediaSets.size
This switches the assignment of Koha.prefs.HTML5MediaEnabled from a non existant template param HTML5MediaEnabled to Koha.Preference('HTML5MediaEnabled').
Also a straying template param HTML5MediaSets.size was left in a JS block.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:05 +00:00
Jonathan Druart 8147f968aa
Bug 42282: Avoid JS error
Fix conflict with "Bug 42177: bundlesEnabled fix"
SyntaxError: redeclaration of const bundlesEnabled

Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:04 +00:00
Paul Derscheid 9db99a08e2
Bug 42282: (follow-up) Switch Coce timeout log to console.warn
qa script flags the stray console.log in the Coce wait-for-images
helper under forbidden_patterns. The line is a genuine soft-failure
diagnostic (Coce never resolved after 5s), matching the
console.warn pattern used for AJAX failures elsewhere in the staff
templates.

To test:
- qa script no longer flags forbidden_patterns on detail.tt.
- With IntranetCoce enabled but Coce unreachable, loading a biblio
  logs "Could not retrieve the images" as a warning after ~5s.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:03 +00:00
Paul Derscheid 774c8d1e94
Bug 42282: (follow-up) Fix broken mailto concatenation in Elasticsearch 404 modal
The port folded the admin email into a single string literal
    " <a href='mailto: + Koha.prefs.KohaAdminEmailAddress'>"
so the href became the literal text "mailto: + Koha.prefs..." instead
of mailto:<admin email>.

Close the string and concatenate Koha.prefs.KohaAdminEmailAddress.

To test:
- Set KohaAdminEmailAddress syspref.
- SearchEngine = Elasticsearch, open a biblio detail page.
- Trigger the Elasticsearch preview with a bad id so the 404 branch
  renders, and confirm the "Send email" link points at mailto:<email>.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:02 +00:00
Jonathan Druart f645702cd8
Bug 42282: Tidy all script tags - catalogue (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:01 +00:00
Jonathan Druart ee299e4b86
Bug 42282: Tidy all script tags - catalogue
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the catalogue module.

The 7 main changes are in the following files:
* ISBDdetail.tt
* MARCdetail.tt
* detail.tt
* imageviewer.tt
* labeledMARCdetail.tt
* moredetail.tt
* stockrotation.tt

Several identical changes (logged_in_user_borrowernumber, searchid,
biblio_id, browser, etc.)
detail.tt needs to be tested a bit more thoroughly but the changes are
still trivial.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:20:00 +00:00
Owen Leonard ea62f3b835
Bug 40610: Update OPAC templates to improve ease of translation
This patch updates the markup structure of some OPAC include files in
order to make the translation process easier.

To test, apply the patch and test that the correct strings are
translatable. In this example I'm testing fr-FR:

- Update a translation:

  > gulp po:update --lang fr-FR
  > perl misc/translator/translate update fr-FR

- Open the corresponding .po file for the strings pulled from
  the include file and locate the updated strings.

  - misc/translator/po/fr-FR-messages.po
    - opac-tmpl/bootstrap/en/includes/biblio-title.inc lines 4 & 5
    - opac-tmpl/bootstrap/en/includes/date-format.inc lines 4, 6, 8
    - opac-tmpl/bootstrap/en/includes/holds-table.inc lines 132 & 135
    - opac-tmpl/bootstrap/en/includes/masthead.inc line 498

  - misc/translator/po/fr-FR/fr-FR-opac-bootstrap.po
    - opac-tmpl/bootstrap/en/includes/biblio-title-head.inc
    - opac-tmpl/bootstrap/en/includes/holds-table.inc line 129 & 141

- Edit the "msgstr" string however you want (it's just for testing).
- Remove the "fuzzy, " text if it's there.
- Install the updated translation:

  > perl mis/translator/translate install fr-FR

- Switch to your translated language in the OPAC.
- Confirm that the strings you translated show up correctly on these
  pages:
  - The page title when viewing a bibliographic record detail page for
    a record without a title.
  - On a user summary page, under the hold tab:
    - A hold that is waiting
    - A hold that is in transit
- In Administration -> Identity providers define a new identity provider
  (it doesn't have to be functional).
  - In the OPAC, test that the "Log in with [identity provider]" link is
    correct in each of the three login forms:
    - The modal triggered from the link in the header
    - The form in the right-hand sidebar
    - The form you see if you navigate directly to a user-related page
      e.g. /cgi-bin/koha/opac-user.pl
- The changes to date-format.inc are only visible if JS is turned off
  because Flatpickr hides the format hint when it loads.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:59 +00:00
Jan Kissig f7941aee28
Bug 42278: (QA follow-up) Fix HIGHHOLDS to JSON serialisation
Using Koha::Template::Plugin::To to serialize HIGHHOLDS to JSON led to the following error:  Template process failed: undef error - encountered object '2026-05-31T23:59:00', but neither allow_blessed, convert_blessed nor allow_tags settings are enabled (or TO_JSON/FREEZE method missing) at /kohadevbox/koha/Koha/Template/Plugin/To.pm line 32.
This patch fixes the handling of HIGHHOLDS.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:57 +00:00
Jan Kissig e0aee23fc1
Bug 42278: (QA follow-up) Fix Uncaught TypeError in request-article
This fixes an Uncaught TypeError when searching for a patron and the search only returns a single patron.

To test:
 a) go to /cgi-bin/koha/circ/request-article.pl?biblionumber=262
 b) ienable dev tools console
 c) enter 'acev' and click Search
 d) The following error appears: Uncaught TypeError: can't access property "on", patrons_table is undefined
 e) apply patch and redo a) to c). The error is not thrown.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:56 +00:00
Jan Kissig c379c8301d
Bug 42278: (QA follow-up): Split JS to separate TT and tidy
This puts ArticleRequestsSupportedFormats in a separate script tag. Auto tidy

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:55 +00:00
Paul Derscheid 0361fee0e4
Bug 42278: (follow-up) Guard bare-RHS TT interpolations
Several unquoted const assignments rendered to `const X = ;` when
their TT expression was empty (patron.borrowernumber without a
patron, CAN_user_circulate_manage_bookings absent, or the
AlwaysLoadCheckoutsTable syspref unset). The parser aborted the
whole script block, so has_patron, AlwaysLoadCheckoutsTable, etc.
were never defined and later scripts failed with ReferenceError.

Add TT fallbacks so the RHS is always a valid JS literal.

To test:
- /cgi-bin/koha/circ/circulation.pl with no patron → no console errors
- Search a patron, check out an item → still works

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:54 +00:00
Paul Derscheid 72443454b2
Bug 42278: (follow-up) Restore findborrower autofocus when no patron loaded
The JS port inverted the original UNLESS condition, so the patron
search field was focused only after a patron was already selected.
Flip back to !borrowernumber.

To test:
- /cgi-bin/koha/circ/circulation.pl with no patron → cursor in findborrower
- Select patron, reload → no focus jump

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:53 +00:00
Jonathan Druart 7cca8e81a2
Bug 42278: Tidy all script tags - circ (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:52 +00:00
Jonathan Druart 650a8d8a64
Bug 42278: Tidy all script tags - circ
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the circulation module.

The 4 main changes are in the following files:
* circulation.tt
As expected this one is messy to test, we need to look at the diff and
make sure the different features linked with the variables moved at
still working correctly (yes, it's vague!)
* circulation_batch_checkouts.tt
On the batch checkout table the due date hidden input should have the
value of the return date high demand items.
* overdue.tt
On the overdue report view, the "Return claims" column must be hidden if
ClaimReturnedLostValue is not set.
* transfers_to_send.tt
The transfers to send view should show all libraries

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:51 +00:00
Pedro Amorim acfd02af12
Bug 41950: (QA follow-up): Add more tests
The tests were lacking a 'real' scenario proving the
sorting on a _counts works at the DB level

prove t/Koha/REST/Plugin/Query.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:50 +00:00
Tomás Cohen Arazi 713c5c3343
Bug 41950: (follow-up) Add DBIC relationship aliases for +count embeds
Adds DBIC relationship aliases so that +count embeds advertised in
the API specs are backed by actual database relationships, making
them sortable via the SQL COUNT subquery infrastructure.

New aliases:
- Borrower: checkouts (issues)
- Aqbookseller: baskets (aqbaskets), invoices (aqinvoices)
- Ticket: updates (ticket_updates)
- ErmEholdingsPackage: resources (erm_eholdings_resources)
- Biblio: holds (reserves)

Koha methods are updated to use the new relationship names directly,
following the one-liner style: Koha::Foo->_new_from_dbic( scalar ... )

Note: checkouts on Borrower and subscriptions on Aqbookseller already
had matching DBIC relationships. comments on Illrequest, requests on
IllBatch, and items on Biblio also already existed.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
   k$ prove t/Koha/REST/Plugin/Query.t \
       t/db_dependent/api/v1/patrons.t \
       t/db_dependent/api/v1/acquisitions_vendors.t \
       t/db_dependent/Koha/REST/Plugin/Objects.t \
       t/db_dependent/Koha/Biblio.t
=> SUCCESS: All tests pass
3. Verify sorting works:
   GET /api/v1/patrons
   x-koha-embed: checkouts+count
   _order_by: -me.checkouts_count
=> SUCCESS: Patrons sorted by checkout count
4. Sign off :-D

Assisted-by: Sonnet 4.6 (Anthropic)
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:49 +00:00
Tomás Cohen Arazi e51334604f
Bug 41950: Make +count embeds sortable via SQL COUNT subqueries
Currently, +count embeds (e.g. claims+count) are computed in Perl via
$object->$relation->count during to_api serialization. This means they
cannot be used in _order_by and each count triggers a separate SQL query
per row (N+1).

This patch makes dbic_merge_prefetch detect +count embeds and, when a
matching DBIC relationship exists, inject a correlated COUNT subquery
via +select/+as instead of adding a prefetch.

The order_by entries referencing count aliases are fixed up to strip the
me. prefix, since +as aliases are virtual columns not bound to a table.

In to_api, the is_count handler now checks for the pre-computed column
via get_column before falling back to the Perl-level ->count call. This
preserves backward compatibility for Koha-level methods that don't have
a corresponding DBIC relationship.

Test plan:
1. Apply patches
2. Run:
   $ ktd --shell
   k$ prove t/Koha/REST/Plugin/Query.t \
       t/db_dependent/Koha/Object.t \
       t/db_dependent/Koha/REST/Plugin/Objects.t
   => SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:48 +00:00
Tomás Cohen Arazi 9bab898922
Bug 41950: Unit tests for sortable +count embeds
Add tests to verify that +count embeds produce +select/+as attributes
instead of a prefetch, enabling SQL-level sorting by count columns.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 16:19:45 +00:00
Marcel de Rooy 1c9e5ed219
Bug 42321: Improve handling of empty Z3950/SRU search results
Skips the empty results; only reports the 'real failures'.
Includes adding a grand total of hits to the form.
Some preliminary work to remove the hardcoded 20 results per target
in the code.
This is not yet a complete solution, but an improvement. The code in
C4/Breeding needs a larger overhaul. (And there might be an issue on
the Folio server side at LOC.)

Test plan:
Without this patch:
Use the LOC SRU server (since if frequently generates errcode 61).
Try the same search several times in a row (with Go). You probably will
come across the "LOC SRU record x: 0" messages. (Random behavior.)
Apply this patch.
Repeat the above. Notice that you will sometimes see the error 61 (and the
tip to refresh) but not the code 0 messages.
Run t/db../Breeding.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:39:10 +00:00
Olivia Reynolds ea67056197
Bug 38621: Use GetPreparedLetter in EmailReport
As with guided reports through the admin interface, this patch switches
EmailReport to use the same function for processing templates. This
gives patron emails sent via cronjobs access to the same variables as
other report locations.

Test plan:
1. Under Tools > Notices and Slips, create a new notice in the Reports
   module. Choose a code and a name, and under Email assign a subject
   and the following body:

Date: [% today %]

2. Under Reports, create a new report from SQL with a name and the
   following query:

SELECT 1 AS borrowernumber, 'to@example.com' AS email

3. Run the following command from the base of Koha, replacing CODE with
   the notice code created earlier, and ID with the report ID created
   earlier:

misc/cronjobs/patron_emailer.pl -module report -from from@example.com -notice CODE -report ID

4. Verify that the report runs successfully, and the console output
   includes "Date:" on a line with nothing following it.
5. Apply patch.
6. Run the following command from the base of Koha, replacing CODE with
   the notice code created earlier, and ID with the report ID created
   earlier:

misc/cronjobs/patron_emailer.pl -module report -from from@example.com -notice CODE -report ID

7. Verify that the report runs successfully, and the console output
   includes "Date:" on a line with the current time following it.
8. Run `prove t/db_dependent/Reports/Guided.t`, and verify it passes.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:39:09 +00:00
Olivia Reynolds cbbfa8c8d2
Bug 38621: Use GetPreparedLetter for report templates
Previously, report templates were rendered using process_tt directly.
This made the available variables inconsistent with other notices and
slips. This patch switches to rendering report templates through the
same function as other notices. To match the arguments to this system,
this patch also makes the report system reference templates by their
code rather than ID.

Test plan:
1.  Under Tools > Notices and Slips, create a new notice in the Reports
    module. Choose a code and a name, and under Email assign a subject
    and the following body:

[% today %]
[% FOREACH d IN data %]
[% d.title %]
[% END %]

2.  Under Reports, create a new report from SQL with a name and the
    following query:

SELECT * FROM biblio LIMIT 1

3.  Create another report from SQL with a different name and the
    following query, specifying a runtime parameter:

SELECT * FROM biblio WHERE 'a'=<<Param>> LIMIT 1

4.  On the first report, select "Run with template" and choose the
    template created earlier.
5.  Verify that the report runs successfully, and the output does not
    include the current date.
6.  On the second report, select "Run with template" and choose the
    template created earlier. Choose "a" as the runtime parameter value
    when asked.
7.  Verify that the report runs successfully, and the output does not
    include the current date.
8.  Apply patch.
9.  On the first report, select "Run with template" and choose the
    template created earlier.
10. Verify that the report runs successfully, and the output now
    includes the current date along with the fetched data.
11. Under the Download dropdown, select "Rendered template (...)".
12. Verify the downloaded file matches the report output.
13. On the second report, select "Run with template" and choose the
    template created earlier. Choose "a" as the runtime parameter value
    when asked.
14. Verify that the report runs successfully, and the output now
    includes the current date along with the fetched data.
15. Under the Download dropdown, select "Rendered template (...)".
16. Verify the downloaded file matches the report output.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:39:08 +00:00
Jonathan Druart d77897c203
Bug 42453: Remove warning
[WARN] Use of uninitialized value $index_count in numeric ne (!=) at /kohadevbox/koha/about.pl line 899.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:39:07 +00:00
Jonathan Druart a1f8f35b70
Bug 42453: Prevent 500 on the about page if elastic is not runing
If the elastic countainer is stopped, the about page crashes with a 500
"No nodes are available"

Test plan:
Startup ktd with `--search-engine es8`
Go to the about page
Notice that the "Elasticsearch" line contains the version and "running"
Stop the es container: `docker start koha-es-1`
Refresh the about page
=> Without this patch you get the 500
=> With this patch applied the page is rendered with:
"Version: Unkown" and "Status: not running"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:39:06 +00:00
Martin Renvoize 88fd58dd6c
Bug 42645: Add tests for FallbackToSMSIfNoEmail with alternate email fields
Verify that the SMS fallback in ModSuggestion() respects EmailFieldPrimary
and EmailFieldPrecedence: a patron with smsalertnumber and an emailpro
address (but no borrowers.email) must receive the notice by email, not SMS.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:39:05 +00:00
Martin Renvoize 57e110ee2c
Bug 42645: Use notice_email_address in ModSuggestion SMS fallback check
The FallbackToSMSIfNoEmail transport selection in ModSuggestion() was
checking $patron->email (the raw borrowers.email column) rather than
$patron->notice_email_address, which respects the EmailFieldPrimary and
EmailFieldPrecedence system preferences. Patrons with only emailpro or
B_email set were incorrectly receiving suggestion status-change notices
via SMS even when a valid email address was available.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:39:04 +00:00
Lucas Gass c0477bc9f1
Bug 42909: Use createdRow to add back suspend class
To test:
0/ APPLY PATCH
1/ View a record with some holds on it
2/ Look at the holds table ( reserve/request.pl?biblionumber=32 )
3/ Suspend at least one hold and notice the 'suspend' class on the <tr>
4/ Unsuspend the hold and notice the class is removed

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:39:03 +00:00
Paul Derscheid fc5ce2dd7e
Bug 41946: Allow superlibrarians to set protected flag on patron creation
Since bug 36085, the protected flag in a patron account was removed from
the patron creation form, regardless of if the user is a superlibrarian.

- Hoist $logged_in_user lookup before the edit_form block
- Move superlibrarian capability checks after the block so they apply
  to all operations including patron creation

To test:
1. Sign in as a superlibrarian
2. Go to Patrons > New patron > choose any category
3. Without this patch: the "Protected" yes/no field is missing
   from the "Library management" section
4. Apply this patch
5. Repeat step 2
   - The "Protected" yes/no field is now visible
6. Create the patron with Protected set to "Yes"
7. Confirm the patron is saved as protected
8. Sign in as a non-superlibrarian with edit_borrowers permission
9. Repeat step 2
   - The "Protected" field should still be hidden
10. Edit an existing patron as superlibrarian
    - The "Protected" field is visible and functional
11. Edit an existing patron as non-superlibrarian
    - The "Protected" field is hidden
12. Sign off

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:39:02 +00:00
Martin Renvoize 38cbe6b195
Bug 41709: Fix EDIfact special character escaping in GIR segments and other fields
Apply encode_text function to all text fields in EDIfact Order messages
to properly escape special characters (?, ', :, +) that could break
message parsing. Previously only bibliographic description fields were
escaped, but GIR segments containing collection codes, branch codes,
item types, call numbers, and other data were vulnerable.

Test plan:
- Verify all existing tests pass
- Added comprehensive tests for special character escaping in GIR segments
- Confirmed that fields containing ?, ', :, + are properly escaped

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:39:00 +00:00
Kyle M Hall b302481a4d
Bug 42862: Use current-relative dates in bookingsModalDatePicker_spec.ts
The booking date picker tests fake "today" with cy.clock and create bookings on
hardcoded June 2026 dates. The modal fetches existing bookings from from
the api and that query uses the real current time, not the fake one in the browser.
Once the real date passes the hardcoded dates, the server stops returning those bookings (
they are now in the past ), so the modal sees no conflicts and assertions such as "the date
should have the leadDisable class" fail.

This patch anchors each test's "today" to the start of next month based on the current
date so the bookings always stay in the future and are returned no
matter when the test runs.

The 'any item' test also sets cy.clock before its second page reload
because the freeze does not survive the reload.

Test Plan:
1) On main run:
   yarn cypress run --spec t/cypress/integration/Circulation/bookingsModalDatePicker_spec.ts
2) Note the test fails
3) Apply this patch
4) Run the command again
5) Note all tests pass!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:38:59 +00:00
Jonathan Druart 761db4772a
Bug 42654: Split on several lines for readability
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:38:58 +00:00
Martin Renvoize ead19931db
Bug 42654: Restore section in OPAC course reserve detail title, breadcrumb and heading
When a course reserve has a section defined, the OPAC course detail page
should display it alongside the course name in the page title, breadcrumb,
and main heading. A previous change inadvertently removed this, leaving only
the course name in all three places.

This patch restores the conditional logic so that:
- If a section is set, the format "Course reserves for 'Course name' - Section"
  is used in the title, breadcrumb and <h1>.
- If no section is set, the existing "Course reserves for 'Course name'"
  format is preserved.

Test plan:
1. Enable the CourseReserves system preference.
2. In the staff interface, go to Course Reserves and create (or edit) a course
   that has a 'Section' value filled in (e.g. "Section A").
3. Add at least one item to that course reserve.
4. In the OPAC, browse to Course Reserves and click through to the detail
   page for that course.
5. Confirm that the browser tab title, the breadcrumb trail, and the page
   heading all show "Course reserves for '<course name>' - <section>".
6. Edit the course and clear the Section field.
7. Revisit the detail page and confirm that all three locations now show
   only "Course reserves for '<course name>'" with no trailing dash or
   section fragment.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@gmail.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:38:57 +00:00
David Nind eb8b13f429
Bug 42721: Update OPACAuthorIdentifiersAndInformation description and identifier labels
The patch updates the OPACAuthorIdentifiersAndInformation system preference
description so that identifiers are listed in alphabetical order.

It also updates the identifier labels in the system preference
description and what is shown in the OPAC:
- ScopusID --> Scopus ID
- WIKIDATA ID --> Wikidata ID

Test plan:
1. For the OPACAuthorIdentifiersAndInformation system preference, select
   "Identifiers (024$2$a)".
2. Note that in the system preference description the identifiers shown
   in the note are not in alphabetical order.
3. Add identifiers to an authority record in the 024$2 (the code for the
   identifier) and 024$a (the value for the identifier) subfields:
   - Example authority record for authority #483, Wall, Larry:
     . ISNI: isni, 0000000110763894
     . Scopus ID: scopus, 57221714177 (note: not his real Scopus ID)
     . VIAF ID: viaf, 91402770
     . Wikidata ID: wikidata, Q92597
4. View the OPAC authority record details page for an authority record
   with 024$2 and $024a information:
   - Example authority record for authority #483, Wall, Larry
     /cgi-bin/koha/opac-authoritiesdetail.pl?authid=483
5. In another tab view the author information tab for an OPAC
   bibliographic record details page where the author is linked to an
   authority record with 024$2 and $024a information:
   - Example bibliographic record details page for Programming Perl:
     /cgi-bin/koha/opac-detail.pl?biblionumber=262
6. Note for both steps 4 and 5 that:
   - Scopus ID is shown as ScopusID
   - Wikidata ID is shown as WIKIDATA ID
7. Apply the patch.
8. Refresh the OPAC authority record and bibliographic record pages.
9. Note that the identifier labels are now shown as Scopus ID and
   Wikdiata ID.
10. View the OPACAuthorIdentifiersAndInformation system preference
    description and note that the identifiers in the note are now
    listed in alphabetical order.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Olivia Reynolds <olivia.reynolds@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:38:56 +00:00
David Nind fff08df007
Bug 40603: (follow-up) Update system preference description - add uri
Adds uri (URI) to the note for the OPACAuthorIdentifiersAndInformation
system preference note.

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:38:55 +00:00
Owen Leonard 91f3fde0de
Bug 40603: Add support for "uri" to OPACAuthorIdentifiersAndInformation
This patch adds support for display of the "uri" identifier in the OPAC
when looking at personal name authority records and associated
bibliographic records.

To test you must have:
1. A personal name authority record
2. A bibliographic record which has 100$a linked to that authority
   record.

- Search for OPACAuthorIdentifiersAndInformation and make sure that
  "Identifiers (024$2$a)" is checked.
- Go to Authorities and locate your author's record.
- Add a 024 tag with "uri" as the source in $2, and a URI in $a.
- In the OPAC, locate the bibliographic record you modified.
- On the detail page, check the "Author information" tab
- Go to the authority detail page in OPAC for the author authority
  record you modified. Check the "Author information" section
  in the right-hand column.
  - In both cases you should see the URI you added, labeled with "URI:"

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:38:54 +00:00
Jan Kissig 8ab021665b
Bug 42912: Fix Uncaught TypeError on opac-readingrecord when no Checkout history
This prevents an 'Uncaught TypeError: can't access property "DataTable", table is undefined' when a patron has no items checked out yet and is visiting opac-readingrecord.
The code now checks if table is defined before accessing its properties.

Testplan:
a) log in in opac, open dev console
b) go to /cgi-bin/koha/opac-readingrecord.pl
c) without patch applied the error above occurs
d) apply patch and reload opac-readingrecord.pl, there should not be any error now
e) check out an item in staff to that patron and check that the table on opac-readingrecord.pl is diplayed

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:38:53 +00:00
Kyle M Hall f7197b43f0
Bug 42664: Unit tests
Test Plan:
1) prove t/db_dependent/SIP/SIPServer.t

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:38:52 +00:00
Kyle M Hall d709c04f57
Bug 42664: Update SIP2 config timestamp after storing the change
Koha::SIP2::Object::store bumps the timestamp before SUPER::store,
opening a race condition where a worker reloading during db update
transaction caches a config that is missing the new row.

Bump the timestamp after the row has been written so it is not advanced
ahead of the data it represents.

Test Plan:
1) prove t/db_dependent/Koha/SIP2/SIP2ModuleMigration.t
2) prove t/db_dependent/api/v1/sip2_accounts.t

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:38:51 +00:00
Kyle M Hall f899761666
Bug 42664: Reload SIP2 configuration in every worker process
SIP2 accounts/institutions are stored in the database and the SIP server is supposed to reload its configuration when they change.

In practice, after adding or editing a SIP2 account, most SIP worker processes keep serving the old configuration (e.g. new accounts get "SIP access not authorized") until the SIP server is restarted ( I tested multiple times over 30 minutes ).

There seem to be two issues:
* L1 cache flushed too late. process_request() calls _config_up_to_date() before Koha::Caches->flush_L1_caches(). The SIP parent reads sip2_resource_last_modified into its own L1 cache at startup and never flushes it. Every worker inherits that stale L1, so a worker's first connection compares against the startup-era value instead of the current shared value.

* sip2_config_read_timestamp is stored in the shared (memcached) cache, but the configuration it tracks is per-process. When one worker reloads the config from the database, it bumps the shared timestamp, so every other worker then evaluates as "up to date" and never reloads its own stale in-memory config.

Test Plan:
1) Configure the SIP server with several worker processes (e.g. set
   min_servers and max_servers to 5) and confirm an existing SIP2
   account works
2) Add a new SIP2 account in the staff interface
3) Without restarting the SIP server, test the new account with misc/sip_cli_emulator.pl
4) Note the login fails
5) Apply this patch
6) Restart all the things!
7) Add another new SIP2 account
8) Without restarting the SIP server test this new account with sip_cli_emulator.pl
9) Note that the new account works!
10) prove t/db_dependent/SIP/SIPServer.t

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-29 15:38:47 +00:00
Jonathan Druart fa92634932
Bug 42281: Tidy all script tags - authorities (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:57:04 +00:00
Jonathan Druart f4353bc132
Bug 42281: Tidy all script tags - authorities
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the authority module.

The 3 main changes are in the following files:
* detail.tt
* merge.tt
* searchresultlist-auth.tt
diff is trivial

Note that authorities/authorities.tt is on a separate bug (bug 42284)

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:57:03 +00:00
Jonathan Druart cc73486ab6
Bug 41254: Add '(all)' for top-level entries
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:57:02 +00:00
Jonathan Druart ae0683fd72
Bug 41254: Replace '*' with arrow
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:57:01 +00:00
Jonathan Druart fd935fa7d7
Bug 41254: Allow filtering by all authorised values for multi-valued statuses
Some item statuses can correspond to multiple authorised values: Not
for loan, Withdrawn, Lost and Damaged. Until now, only a single entry
could be selected, and filtering applied to all values at once.

This change introduces finer-grained filtering by exposing each
authorised value individually in the filter.

Test plan:
Assign multiple authorised values to items on a bibliographic record
for the four affected attributes: Not for loan, Withdrawn, Lost and
Damaged. Use the "Status" filter and verify that each value behaves as
expected.

Display note:
The current dropdown presentation is not ideal.
Suggestions for improvement are welcome, although avoiding optgroup
would be preferable to keep the scope small.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:57:00 +00:00
Jonathan Druart 391ef08e30
Bug 42080: Add Cypress tests
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:56:59 +00:00
David Cook 9a39e4799a
Bug 42080: (follow-up) limit data sent to libmagic to 100 characters
By limiting the data sent to libmagic to 100 characters, we make
the test faster and we avoid situations where a large or malicious
file causes too many resources to be used.

100 characters is more than enough for the file types we're allowing.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:56:58 +00:00
David Cook 1c6328b82b
Bug 42080: (follow-up) allow some file types, checked by libmagic, to be viewed inline
This patch uses File::LibMagic to determine the real file type using
libmagic and magic numbers of invoice files, and allows a select group
of file types to be viewed inline for user convenience.

Note that a default Content-Security-Policy is still included to offer
an extra layer of protection against XSS.

Test plan:
0a. Apply the patch
0b. koha-plack --restart kohadev
1. Enable the syspref AcqEnableFiles (ie set to Do)
2. Find or create an active vendor
3. Create a basket if one doesn't already exist
4. Receive a shipment for the vendor
5. Create an invoice
6. Click "Finish receiving"
7. Click "Manage invoice files"
8. Upload a range of files including SVG, PDF, PNG, TXT
9. Click on the "Download" button
10. Note that the file downloads for all types
11. Click on the link in the "Name" column
12. Note that only the PDF and PNG display inline in the browser.
The other file types should be downloaded same as if you clicked the
"Download" button.

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:56:57 +00:00
Eric Phetteplace 0161a73d17
Bug 42080: Invoice PDFs are forced downloads
Serve invoice files with a CSP header that prevents XSS
instead of with an attachment header that turns them into
downloads.

Testing plan:
- Set AcqEnableFiles preference to Do
- Acquisitions > Vendor > Receive Shipment > Create invoice
- Finish Receiving > Manage invoice files
- Upload an SVG with a <script> tag in it
- Click the SVG filename to open it
- Inspect browser's console: it should say something about a
script being blocked due to the CSP directive

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:56:56 +00:00
David Cook a0136e5f2e
Bug 42087: Add dependency to debian/control
This change adds File::LibMagic (via libfile-libmagic-perl)
to debian/control. The debian/control should be automatically
re-generated by debian/control.in during the package process,
but we want to record the change here anyway.

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:56:55 +00:00
David Cook 76ecdf9e6d
Bug 42087: Add dependency to cpanfile
This change adds File::LibMagic to cpanfile

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:56:54 +00:00
Jonathan Druart 711b3fbd83
Bug 42286: Tidy all script tags - ill (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:56:53 +00:00
Jonathan Druart 71cd1efd7d
Bug 42286: Tidy all script tags - ill
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the ILL module.

I don't really know how to test this patch...

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-26 11:56:52 +00:00
Pedro Amorim c355bb8574
Bug 42666: (RM follow-up): Tidy datatables.js
prove xt/js_tidy.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-24 08:41:35 +00:00
Jonathan Druart ce20c17e4e
Bug 42564: Fix Template/Plugin/Asset.t
lib/fontawesome/css/fontawesome.min.css was used as an example for this
test, let's use datatables.min.css

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-24 08:41:31 +00:00
Jan Kissig 293e00e95c
Bug 42357: Show patron details on holds table
This patch reintroduces patron details in 'Existing holds' table by using $patron_to_html.

test plan:
a) get a nice fresh ktd and navigate to /cgi-bin/koha/reserve/request.pl?biblionumber=262
b) place a hold for a patron f.e. 23529000035676
c) take a look at the Existing holds tables patron column which only shows the patrons card number.
d) apply patch and run 'yarn js:build'
e) refresh /cgi-bin/koha/reserve/request.pl?biblionumber=262
f) the patron column now should show patrons name
g) set system preference  HidePatronName  to 'Don't show'
h) go to /cgi-bin/koha/reserve/request.pl?biblionumber=262
f) the patron column now should only show the patrons card number.

Signed-off-by: Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 16:26:38 +00:00
Fridolin Somers 6e51cdae0d
Bug 42640: Script search_for_data_inconsistencies.pl use binmode UTF-8
Like Bug 32012 for runreport.pl the script search_for_data_inconsistencies.pl should use binmode UTF-8 to correctly display datas.

Test plan:
1) Edit an authorised value to add some diacritics in code, in "LOC" for example
2) Edit an item to use this code
3) Delete this authorised value
4) Run misc/maintenance/search_for_data_inconsistencies.pl
=> Check you have a correct message with diacritics

Signed-off-by: Chris Mathevet <chris.mathevet@inLibro.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 16:26:38 +00:00
Adolfo Rodríguez 676e01d346
Bug 42825: ERM Local Title - Start date (started_on) not saved for package resources
The started_on field inside relationshipFields was missing indexRequired: true,
which is required to generate a unique DOM id per row and correctly bind the
flatpickr input to the Vue data model.

The date_to componentProp also needs indexRequired: true so it references the
correct indexed id of the ended_on field.

Additionally, add a null check in calendar.inc before accessing ._flatpickr
on the end date element to prevent a TypeError when the linked element is
not found.

To reproduce:
1. Go to ERM > eHoldings > Local > Titles
2. Create or edit a local title
3. Link it to a package and enter a Start date, End date and Proxy
4. Save
5. Reopen the title and click on the linked package row
=> Start date is empty; End date and Proxy are saved correctly

Apply patch, then:
1. Go to ERM > eHoldings > Local > Titles
2. Create or edit a local title
3. Link it to a package and enter a Start date, End date and Proxy
4. Save
5. Reopen the title and click on the linked package row
=> Start date is now saved and displayed correctly
=> No TypeError in the browser console when selecting a start date

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 16:26:37 +00:00
Janusz Kaczmarek 3bfd382566
Bug 42159: Unit tests
Unit tests

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 16:26:36 +00:00
Janusz Kaczmarek 9071c83d86
Bug 42159: With OPACAuthorIdentifiersAndInformation information lacking from field 110/111
When the OPACAuthorIdentifiersAndInformation system preference is
enabled, selected information about authors is displayed. However, if
the main author is a corporate body or a meeting/conference (MARC 21
fields 110/111), the information is not shown.

Test plan:
==========
1. Enable the display of identifiers in
   OPACAuthorIdentifiersAndInformation.
2. Add a 024 field with a VIAF ID to any corporate authority record,
   e.g.:
   024 7# $a 1234 $2 viaf
3. Use this authority heading as the main entry (110 or 111) in any
   bibliographic record.
4. In the OPAC view of that bibliographic record, verify that the Author
   information tab does not display information from the main entry.
5. Use the same heading as an added entry (710 or 711). In the OPAC
   view, verify that the Author information tab now displays information
   from the added entry, but still not from the main entry.
6. Apply the patch ; restart_all.
7. Verify that OPAC now displays information from the 110/111
   heading fields in the bibliographic record.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 16:26:35 +00:00
Paul Derscheid 78630a1a82
Bug 42231: (QA follow-up) Show item title in note and suspend-hold modals
The static 'hidden' attribute on #addNoteTitle and #suspendHoldTitle was never
removed, so the item title was populated but stayed invisible.

- Toggle 'hidden' off when a modal is populated with a title
- Toggle 'hidden' on when a modal is closed/cleared

Test plan:
- Enable AllowCheckoutNotes
- OPAC > Summary > Checked out: click 'Add note', confirm the item title shows
- OPAC > Summary > Holds: click 'Suspend', confirm the item title shows
- Reopen each modal on a different item, confirm the title updates
- Run an accessibility checker, confirm no empty-heading errors return

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 16:26:34 +00:00
Laura_Escamilla bfa3956cd3
Bug 42231: Fix accessibility issues in OPAC summary table
The OPAC summary page contains several accessibility issues including an unlabeled renew checkbox, unlabeled textarea in the add note modal, and empty headings rendered before JavaScript population.

Test plan:
1. Have a user with checkouts
2. Log into the OPAC and go to "Summary"
3. Notice the renew checkbox does not have a corresponding label
4. Apply the patch
5. Repeat steps 1-2
6. Confirm the renew checkbox now has a corresponding label

Additional accessibility fixes:
7. Open the "Report a problem" modal
8. Confirm the note textarea has a corresponding label
9. Confirm addNoteTitle and suspendHoldTitle are not exposed as empty headings
10. Run an accessibility checker and confirm the related errors are resolved
11. Sign off and have a great day! :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 16:26:31 +00:00
Eric Phetteplace a7646d86d3
Bug 42116: e.preventDefault is a function, needs parentheses
This change is unnoticeable in the UI, so the patch does not need before/after testing, but assurance that everything works. The affected code is used in 3 places for holds (cancelling a hold from reserve/request.pl, circ/circulation.pl, and members/moremember.pl) and 2 for cataloging (two different functions on cataloguing/additem.pl).

For holds:
1. Create three holds on different bibs for a patron
2. On the same page where you create the last hold (reserve/request.pl), delete it
3. Circulation > Check out (circ/circulation.pl) > Check out a different title to patron
4. Switch to patron's Holds tab, delete a hold
5. Go to Patrons account (members/moremember.pl) > Holds tab, delete a hold
6. All holds should delete successfully

For cataloging:
1. Go to any bib > New > New item
2. Select "Add Multiple Copies of this Item", add a note
3. Confirm multiple items are created with the note
4. Enter a new note, then select "Save as template"
5. Confirm the item template exists & create an item using it

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 11:49:25 +00:00
Jan Kissig 070693abd3
Bug 42582: Fix Uncaught TypeError on 'Place request with partner libraries'
This patch fixes an Uncaught TypeError on Place request with partner libraries (op=generic_confirm) when at least one partner library is available (primary email is set) and system preference ILLCheckAvailability is enabled.
Reason for this is an undefined object services.

Test plan:
a) start a ktd instance
 ktd --proxy --name main up -d
b) enable system preference ILLModule and ILLCheckAvailability
 http://main-intra.localhost/cgi-bin/koha/admin/preferences.pl?tab=interlibrary_loans
 ILLModule: 'enable'
 ILLCheckAvailability: 'check'
c) edit an ILL patron and set an Primary email address:
 http://main-intra.localhost/cgi-bin/koha/members/moremember.pl?borrowernumber=16 (Walker)
d) create an ill request (Interlibrary loans -> (Requests) -> New ILL request) and fill out mandatory data
e) open dev tools (F12 on Firefox) and go to 'Place requests with partners'
f) notice console error: Uncaught TypeError: can't access property "enabled", services[0] is undefined
g) apply patch and reload page 'Place requests with partners' -> no error

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 11:49:24 +00:00
Jan Kissig 35c7cd1ae7
Bug 42141: Fix Save and show/list/edit button on Vue SIP2 config
This patch fixes the behavior of the Save (Save and list, Save and continue editing, Save and show) button which is controlled via useBaseResource.navigationOnFormSave on
Accounts, Institutions and System preference overrides in Administration -> Self-service circulation (SIP2)

To test:
a) go to Administration -> Self-service circulation (SIP2)
b) create one of the given resources (institution, account, system preference override) and fill out mandatory fields
c) click either Save or Save and continue editing or Save and show
d) check that you always end up on the list.
e) apply patch and run 'yarn js:build' , reload your browser
f) repeat b) and c)
g) check that the desired view is now shown
  - show the edited/created resource
  - list all created resources
  - continue editing the resource

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 11:49:23 +00:00
Jonathan Druart 398c329c21
Bug 42611: Add missing raw filter
[FAIL] koha-tmpl/intranet-tmpl/prog/en/modules/members/readingrec.tt
   FAIL   filters
                asset_must_be_raw at line 172 (    [% Asset.js("js/members/readingrec.js") %])

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 11:49:22 +00:00
David Cook 5c06532231
Bug 42611: Fix Javascript error when no Circulation history
This patch checks that "table" is defined before trying to access
the DataTable object for it.

This patch also moves the Javascript from the .tt file into a separate .js
file since it doesn't need any Template::Toolkit itself.

Test plan:
0. Apply the patch (and reset_all)
1. Log into the staff interface
2. Go to http://localhost:8081/cgi-bin/koha/members/readingrec.pl?borrowernumber=51
3. Open the console and see there is no longer a Javascript error
4. Check out an item like 39999000001310 to this user
5. Review the "Circulation history" and note that the DataTable appears
and there are no Javascript errors

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 11:49:21 +00:00
Jonathan Druart c14daf3f3c
Bug 42666: OPAC counterpart
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 11:49:20 +00:00
Jonathan Druart e82a3c7f96
Bug 42666: Add a Cypress test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 11:49:19 +00:00
Chris Mathevet 487c9701a4
Bug 42666: Parse kohaTable pageLength setting to int
Test plan :
01 - Clear your browser's local storage
02 - Go to Administration > Item types
03- You need to have more than 10 item types, add some if needed
04 - Observe "Showing 1 to 010 of XX" on top of the table
05 - Click on "next" button at the bottom of the table
06 - Nothing happens
07 - Go to Administration > Patron categories
08 - You need to have more than 20 patron categories, add some if needed
09 - Observe "Showing 1 to 020 of XX" on top of the table
10 - Click on "next" button at the bottom of the table
11 - Nothing happens
12 - Apply patch
13 - Clear your browser's local storage
14 - Repeat 2-5 and 7-10, observe that the leading 0 is gone, and that the
"next" button works

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-23 11:49:15 +00:00
Laura_Escamilla 51bdab13ee
Bug 42078: Allow vendor ID searches on serial subscription vendor search page
The serials vendor search currently only searches vendors by name.
This patch allows the search field to also match vendor IDs so
staff can locate vendors using either name or ID.

Test plan:
1. Go to Serials → New subscription.
2. Click to search for a vendor.
3. Search using part of a vendor name and confirm results appear.
4. Search using an exact vendor ID and confirm the correct vendor is returned.
5. Sign off and have a wonderful day! :D

Signed-off-by: Angela Berrett <angela.berrett@familysearch.org>
Signed-off-by: Angela <angela.berrett@familysearch.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:13 +00:00
Pedro Amorim c420f23125
Bug 42002: Make 'replace' in screen_msg_regex not required
Default to empty string if null to ensure that leaving it blank produces the required outcome: the replace value is an empty string

Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:12 +00:00
Baptiste Wojtkowski d294b2ce2a
Bug 39727: Remove DelSuggestionsOlderThan from C4/Suggestions.pm
To test:
Test the cronjob purge_suggestions.pl

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:11 +00:00
Baptiste Wojtkowski b2505f31c3
Bug 39726: Remove op connectDuplicate from acqui/newordersuggestions.pl
ConnectDuplicate does not appear to be used anywhere.
No test plan to provide here.
git grep connectDuplicate should return nothing

Since there is no cud-, it would at least have been broken for a year

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:10 +00:00
Pedro Amorim de837ace63
Bug 41685: (RM follow-up): Remove dead comment code
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:09 +00:00
Paul Derscheid 5cba025ba1
Bug 41685: (QA follow-up) Classify MARC21 $w by position 0
MARC21 4xx/5xx $w is a control subfield with up to four positions;
only position 0 encodes the hierarchical relationship. The previous
regex required the whole subfield to equal g or h, so values such as
"g   " or "gnna" would fall into the middle bucket and lose the
broader/narrower distinction.

This case is hypothetical and likely rare in practice (most catalogs
store a single character in $w), but the correctness improvement still
makes sense and aligns with how BuildSummary reads the same subfield
elsewhere via substr($w, 0, 1).

To test:
- prove -v t/db_dependent/AuthoritiesMarc.t
- Verify BuildSummary/_marc21_sort_hierarchy_alpha subtest passes,
  including the new multi-position $w case.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:08 +00:00
Paul Derscheid 7ce0fef1bd
Bug 41685: (QA follow-up) Add POD for _marc21_sort_hierarchy_alpha
- koha-qa pod_coverage check flagged the new private helper
- sub as uncovered. Document it briefly to satisfy the check.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:07 +00:00
Marcel de Rooy 21f957d868
Bug 41685: (QA follow-up) Prevent uninit warn on missing subfield a
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:06 +00:00
Marcel de Rooy 8f6c66afbb
Bug 41685: (QA follow-up) Resolve qa warnings
Resolve critic warn:
"return" statement followed by "sort" at line 1238, column 5.  Behavior is undefined if called in scalar context.  (Severity: 5)

Tidied with lastest perltidy version.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:05 +00:00
Marcel de Rooy aab711b1b3
Bug 41685: Unit tests
Test plan:
Run t/db_dependent/AuthoritiesMarc.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:04 +00:00
Marcel de Rooy 27fb770e77
Bug 41685: Sort seefrom/seealso in BuildSummary
Test plan (MARC21):
[1] Create an authority record, say a term. Include two 450s, the first
    one starting with Z and second with A. Include five 550s, where two
    are narrower ($w==h), two broader ($w==g) and one without $w. Make
    sure that the terms will sort differently than the field order (go
    back in alphabet when filling subsequent $a's or so).
[2] Perform an authority search that includes the new authority.
[3] Verify that the terms in the Summary column for this authority
    are sorted first on hierarchy, second alphabetically. The one
    without hierarchy should be in the middle.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:03 +00:00
Lisette Scheer 7d60cc280a
Bug 42734: (QA follow-up) Remove integer class
The integer class appears to only lmit the with of the text box to 5em, making it difficult to see the full preference.
I removed the class for better readability.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:02 +00:00
Martin Renvoize 914ff32fa2
Bug 42734: Add autoMemberNumValue to patrons sysprefs UI with caution note
This patch adds autoMemberNumValue to the Patrons system preferences
page immediately below autoMemberNum, so superlibrarians can view and
correct a runaway counter without needing vendor support.

The entry carries a caution note explaining:
- the value is managed automatically and should not normally be changed
- Koha generates the next cardnumber from max(counter, live_db_max),
  so lowering the counter only has effect once the patron responsible
  for the inflated number has been deleted or had their cardnumber
  corrected first
- the risk of setting it too low: potential re-issuance of a previously
  used cardnumber, which may grant a new patron access to third-party
  services linked to the original holder

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:01 +00:00
Jonathan Druart dd89d8ab60
Bug 42803: Only perform the lookups when !$filter is true
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:39:00 +00:00
David Cook d2a76b80b0
Bug 42803: Fix case where filter is not supplied
This change fixes the case where "Any library" and "Any item type"
weren't validating.

Test plan:
0) Apply the patch
1) Set strict_sql_modes to 0 in koha-conf.xml and restart plack
2) Go to Reports > Items with no checkouts:
<staff_url>/cgi-bin/koha/reports/catalogue_out.pl
3) Leave Library set to "Any library"
4) Pick a specific item type that you know has items (e.g. "BK")
5) Note that you get results

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 15:38:57 +00:00
Owen Leonard 25496bdc4f
Bug 42128: Remove event attribute from admin-icon-selection.inc
This patch removes the "onmousedown" event attribute from
admin-icon-selection.inc, an include used in itemtypes.tt and
authorised_values.tt. I took this opportunity to create separate JS
files for both of those templates and added the event handler to the JS.

To test, apply the patch and go to Administration.

- Go to Item types and add or edit an item type.
  - In the icon selection section, select one of the built-in icons.
  - Switch to the "Remove image" tab.
  - Click the text input. The corresponding radio button should be
    selected.
- Go to Authorized values and perform the same test.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:22 +00:00
Fridolin Somers b83e2a6fe4
Bug 42846: Koha::Database::DataInconsistency fix typo for biblioitemnumber in MARC
Check of biblioitemnumber in MARC uses $biblionumber instead of $biblioitemnumber

It returns for example :
Biblionumber 542620 has biblioitemnumber '542620' but should be '542620' in 090$a

Test plan (for MARC21 only) :
1) Choose a biblio record id=X
2) Change with SQL :
update biblio_metadata set metadata = UpdateXML( metadata , '//datafield[@tag="999"]/subfield[@code="d"]' , CONCAT('<subfield code="d">1111</subfield>') ) where biblionumber = X
3) Run audit on this record
4) Check message is correct

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:21 +00:00
Pedro Amorim d59c3b1433
Bug 42397: DBRev 26.06.00.003
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:20 +00:00
Pedro Amorim 25bfb77b0b
Bug 42397: Atomicupdate
1) Enable ILLModule and IllLog system preferences.
2) Install a metadata enrichment plugin that supports ILL batches (restart plack if needed), e.g.:
  https://github.com/openfifth/koha-plugin-api-pubmed/releases
3) Visit ILL module:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl
4) Click the 'New ILL requests batch' button
5) Enter any name, cardnumber '42' and any library. Click 'Continue'
6) Add a pubmedid (e.g. '123') and click process identifiers.
7) Click 'Add items to batch' and 'Finish and view batch'.
8) Visit the ILL request we just created:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?op=illview&illrequest_id=1
9) Confirm it explodes with a nasty 500 error
10) Apply patches, updatedatabase and restart plack.
11) Repeat 8). Confirm it no longer explodes
12) Click' ILL request log'. Confirm it shows the correct log(s) relative to this ILL request
13) Visit the log viewer and query for 'Interlibrary loans':
http://localhost:8081/cgi-bin/koha/tools/viewlog.pl?src=&user=&modules=ILL&actions=&object=&info=&interfaces=&from=&to=&output=screen&basename=Export&report_name=&do_it=1
14) Confirm only ILL requests are shown, do the same for ILL batches:
http://localhost:8081/cgi-bin/koha/tools/viewlog.pl?do_it=1&src=&user=&modules=ILL_BATCHES&actions=&object=&info=&interfaces=&from=&to=&output=screen&basename=Export&report_name=&do_it=1
15) Confirm only ILL batches are shown
16) Run all ILL batch relevant tests:
prove t/db_dependent/Koha/ILL/Batch*

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:19 +00:00
Pedro Amorim 268b5cbd0e
Bug 42397: Add and update ILL batch related tests
Add new tests to assert the usage of the new ILL_BATCHES action_logs modulename.
Update existing tests to utilize real log_something instead of mocking it.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:18 +00:00
Pedro Amorim 0c353be1c1
Bug 42397: Add 'ILL_BATCHES' action_logs modulename
Update ILL/Batch.pm and ILL/Batch/Status.pm to utilize this new modulename instead

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:16 +00:00
Jonathan Druart 10e86285a9
Bug 42535: Remove .news_delete JS from the main page
The .news_delete elements have been removed on
  commit c7b576ad7e
  Bug 34478: Remove 'Delete' link for news on the homepage
- | <a class="news_delete" href="/cgi-bin/koha/tools/additional-contents.pl?op=delete_confirmed&amp;ids=[% koha_new.additional_content_id | html %]&amp;csrf_token=[% csrf_token | uri %]">Delete</a>

We can remove this code

Test plan:
Confirm that there is no "Delete" links to delete news on the main page

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:15 +00:00
Laura_Escamilla f10b232d9e
Bug 42531: Allow repeatable field values to wrap
Test plan
1. Go to Administration → MARC bibliographic frameworks.
2. Edit Books, Booklets, Workbooks (BKS) framework.
3. In tag 508, ensure subfield $a is visible in the editor.
4. Open a bibliographic record (e.g. /cgi-bin/koha/catalogue/detail.pl?biblionumber=90).
5. Edit the record and add a long block of text to 508$a (e.g. extended production credits with many names and separators).
6. Save the record and view it in the staff interface.
    * Before patch: The Production Credits field does not wrap and overflows off the page.
7. Apply the patch and run yarn build.
8. Refresh the record detail page.
    * After patch: The Production Credits field wraps correctly within the page.
9. Confirm that shorter repeatable fields still display inline with separators (|) as expected.
10. Sign off and have a great day!

Signed-off-by: Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:14 +00:00
Lucas Gass 82d189a756
Bug 42795: Do not try to update ES mappings if not using ES patch for main
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:13 +00:00
Jonathan Druart 2d72d4e17a
Bug 42550: Remove JS error from report list if none exist
If there are no saved SQL reports, the "rtable" variable is empty and
triggers an error when called later.
Uncaught TypeError: can't access property "DataTable", rtable is
undefined

Test plan:
/cgi-bin/koha/reports/guided_reports.pl?op=list
Without reports
=> Without this patch there is a JS error in the console
=> With the patch the error is gone

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:12 +00:00
Owen Leonard b503cfe22e
Bug 42564: (follow-up) Actually remove calls to old assets
Also incorporate the Font Awesome assets into the installer SCSS.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:11 +00:00
Owen Leonard 8ffe7d9edb
Bug 42564: Upgrade Font Awesome in the staff interface to 7.2
We are using Font Awesome 6.3 in the staff interface. The current
version is 7.2. Upgrading will eliminate the repeated warnings in the
browser console about "Glyph bbox was incorrect."

The patch updates the staff interface CSS so that it pulls Font
Awesome CSS from node_modules. Font assets have been updated in
lib/fontawesome.

The many changed .inc and .tt files are a result of a missed aspect of
our upgrade to Bootstrap 5. The "sr-only" class was removed in BS5 but
the Font Awesome CSS included its own "sr-only" class. This made up for
the fact that we missed the required upgrade to BS5's "visually-hidden."
As soon as Font Awesome's "sr-only" was gone all these mistakes were
made visible.

To test, apply the patch and run 'yarn install.'
- Rebuild the staff interface SCSS.
- Clear your browser cache.
- Test many pages in the staff interface to confirm that no icons are
  missing.
- Check changes to the templates to confirm that "sr-only" has been
  replaced with "visually-hidden."
  - Places where a broken "sr-only" class would be obvious: The h1
    heading "Koha home" on the staff interface main page, the <legend>
    tag on the new currency form in Administration -> Currencies and
    exchange rates.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:10 +00:00
Owen Leonard 297edd689b
Bug 42641: JavaScript errors on patron attribute types admin page
This patch fixes some JavaScript errors on the patron attributes page.
Vanilla JS doesn't automatically handle null variables like jQuery does.

To test, apply the patch and go to Administration -> Patron attribute
types.

- If necessary, add multiple attribute types.
- The table listing all attributes should be displayed as DataTable and
  there should be no errors in the console.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:09 +00:00
Kyle M Hall 404cb0ee0a
Bug 36988: Update unit tests
These highlight why we may have made this choice. With only 'edit_borrowers' you can make
changes, but you cannot view your changes as you aren't allowed to see other borrowers.

Test now pass, but not sure if this answers the question of why we did it this way, or if there is more
work here. Should you be able to get back the fields that you updated at the least, to confirm your changes? Or
should we send a success with a message that you can't see the data?

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:08 +00:00
Kyle M Hall 3e62b2c44b
Bug 36988: Change permission
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:07 +00:00
David Cook 55d236769d
Bug 42202: Unfilter branches for OPAC search, news, and most popular
This change unfilters the branches for OPAC search, news, and most popular.
This is most noticeable when using the IndependentBranches system preference.
This change aligns the UX for both logged in and not logged in users

__SETUP__
- Do not apply the patch yet
- Log into Koha
- Go to Administration > System preferences
- Search for OpacAddMastheadLibraryPulldown system preference
- Change it to "Add" and save the preference
- Search for IndependentBranches system preference
- Set it to "Yes" and save the preference
- Search for OpacTopissue system preference
- Set it to "Allow" and save the preference
- Search for OpacNewsLibrarySelect system preference
- Set it to "Show" and save the preference
- Go to Patrons
- Create or modify a patron with a username/password but no superlibrarian
permission

__TESTS__
- Go to the OPAC (and logout if you're already logged in)
- Note that the library pull down saying "All libraries" is present
in the masthead
- Log in as your unprivileged user
- Note that the library pull down saying "All libraries" is now hidden
- Go to Advanced search and note there's no "Location and availability" option
- Go to "Most popular" and note there's no "From:" library filter
- Go to the home page and note it does not show a library selector for news

- Apply the patch

- Refresh the page
- Note that the library pull down saying "All libraries" is present
in the masthead
- Note that the "From:" library filter now appears
- Go to Advanced search and note "Location and availability" now appears
- Go to the home page and note it now shows a library selector for news

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-19 12:00:03 +00:00
Olivia Reynolds bb83b600ec
Bug 12757: Set float type on OpenDocument cells
Test plan:
1. Navigate to Reports > Create from SQL.
2. Create a new report with a name and the following SQL query:

SELECT 'test', 1, '2'

3. Run the report.
4. Under the Download dropdown, select "Open Document Spreadsheet".
5. Verify a ".ods" file is downloaded.
6. Open the downloaded spreadsheet.
7. Verify the two number-like columns are handled as such. This depends
   on your choice of spreadsheet software:
   - In Excel, the numbers should not have a leading single quote in the
     edit-bar when selected.
   - In Google Sheets, the numbers should be right-aligned, and
     selecting both of them should display their sum in the bottom
     toolbar.
8. Run `prove t/Koha/Util/OpenDocument.t` and verify all tests were
   successful.

Sponsored-by: Open Fifth
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-18 16:02:11 +00:00
Lucas Gass 4f71f15f0c
Bug 41070: (QA follow-up) Tidy
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-18 16:02:10 +00:00
Baptiste 902ca28a3c
Bug 41070: Fix selector in addorderiso validator
Steps to reproduce, do not apply patch
1 - Go to any record and click save -> marc (Unicode/utf8)
2 - Go to any vendor and add a basket
3 - In your acquisition grid, make sure "barcode" is mandatory
4 - In this basket, create a new order from file, use the generated file and wait for the end of the import. Click on add staged files to basket.
5 - Select the record, make sure no Fund or barcode is filled, neither in first or third tab.
6 - Validate: There will be an error due to the missing barcode. If
there is another error, fill the missing part of the form.
7 - Apply patch, repeat 6, the error will be due to the missing fund
8 - Select a fund in the first panel -> the error will be due to the
missing barcode
9 - Select a fund in the third panel -> the error will be due to the
missing barcode

Note: the point about the barcode are not relevant for the bug, they are
just here to prevent the submission of the form. And avoid repeating
steps 4-5.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-18 16:02:09 +00:00
Martin Renvoize cb993ee5ef
Bug 40225: Send stockrotation report even when all items are stationary
The stockrotation cronjob was only sending email reports when there were
actionable items (items that needed to be moved between branches). This
meant that libraries would not receive any notification when all items
in their rotation were stationary, even when the --send-all flag was
passed to the script.

This patch modifies the report logic to also send reports when there are
only stationary items, ensuring libraries have visibility into the complete
status of their stock rotation items.

Test plan:
1. Set up a stock rotation rota with items that are all stationary
   (items that should stay at their current branch)
2. Run the stockrotation cronjob: perl misc/cronjobs/stockrotation.pl
3. Without this patch: No email report is generated
4. Apply this patch
5. Run the cronjob again
6. Verify that an email report is now generated showing the stationary items
7. Test with mixed scenarios (some actionable, some stationary items)
8. Confirm reports are sent in all cases where items exist in the rotation

Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-18 16:02:08 +00:00
Owen Leonard 369236bb31
Bug 41803: Improvements to consistency in basket groups template
This patch makes some updates to the basket groups template:
- Buttons in a table's "actions" column should have the class "btn
  btn-default btn-xs"
- Adding icons to action buttons
- Group export options (CSV and PDF) under button dropdown
- Use class instead of ID as hook for closing and deleting basket
  groups. This prevents duplicate element IDs on the page.
- When there is no EDIFACT configuration, disable the "Generate EDIFACT
  order" button and put the error message in a tooltip.
- Style warning message as Bootstrap alert

To test, apply the patch and go to Acquisitions and locate a vendor.

- View the vendor details.
- Click "Basket groups" in the sidebar.
- Create multiple basket groups if necessary.
- Test various operations in the list of open groups:
  - Close and export PDF
  - Edit
  - Delete
- Test the actions for a closed basket group:
  - Reopen
  - Export -> CSV and Export -> PDF
- Enable the EDIFACT system preference but do not add any EDI accounts.
  - Under the "Closed" basket group tab the "Generate EDIFACT order"
    buttons should be disabled, and hovering over each one should
    trigger a popup warning that there is no EDIFACT configuration for
    the vendor.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-18 16:02:05 +00:00
Jonathan Druart 38474d7611
Bug 42781: Do not modify window.name
https://developer.mozilla.org/en-US/docs/Web/API/Window/name

Test plan:
0. Do not apply this patch (you can apply the first one)
1. Go to /cgi-bin/koha/members/members-home.pl
2. Open the console
3. type "window.name", it's ""
4. Run a patron search, "d"
5. window.name is now "Duffy, Joann name"
6. Apply this patch, try again
7. window.name is still an empty string

Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:59:36 +00:00
Magnus Enger 98459bab8c
Bug 42781: Declare 'name' before using it in patron-format.js
The code in koha-tmpl/intranet-tmpl/prog/js/patron-format.js uses
a variable called 'name' without declaring it first. The declaration
was removed in bug 32176. No deterioration of functionality has been
discovered in connection with this, but the declaration should be
restored.

To test:
- Apply the patch.
- HidePatronName = Show
- Look at the staff detail view for a record that has at least one
  item on loan.
- Verify the name of the patron is displayed as expected.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:59:34 +00:00
Jan Kissig 4979f23cb1
Bug 42844: Fix broken subscription search when using additional fields as seach criteria
This fixes an error when searching for a subscription with an additional field when no result is found by changing the call of SearchSubscriptions to expect an array instead of an hash ref.
Serials.pm, when using filter_by_additional_fields may return an empty array, but the calling function only checked for an hash ref which lead to an error when no result was found.

Testplan:
a) create an additional field for subscriptions: More -> Serials -> Manage subscription fields
   f.e. use an AV CCODE as additional field
   make sure "searchable" is checked
   No subscription should use this additional field by now.
b) go to More -> Serials
c1) if bug 40201 is pushed you can search for that additional field on serials-home
   -> An error stack trace will appear.
c2) otherwise perform an empty search on serials home to end on serials-search. In the search form on the left select that additional field as search criteria.
   -> An error stack trace will appear.
d) add a subscription using that additional field
  -> More -> Serials -> New subscription.
  On step two there is a section for Additional fields. Select f.e. CCODE Fiction
e) redo c1 or c2 using that additonal field value as search criteria.
f) Check that the search result shows your subscription.
g) apply patch and redo c1 or c2 for an additional field value that is not used (f.e. Non-fiction)
h) The search should work now, returning 0 results instead of an error
i) check that searching for a used additional field (f.e. Fiction) still works

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:59:31 +00:00
Pedro Amorim 2bb3865e7e
Bug 28915: DBIC Schema Build
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:17 +00:00
Pedro Amorim 566ea8876b
Bug 28915: DBRev 26.06.00.002
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:16 +00:00
Pedro Amorim f099cb0d62
Bug 28915: (RM follow-up): Remove dead code + typo fix
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:15 +00:00
Lari Taskula e0fb33c3a7
Bug 28915: Adjust input maxlength when subfield is used as charpos
To test MARC modification with the new feature:
1. Add a new record and make sure it has the following fields
1.1 Field 008 character position 35-37 contains "|||"
1.2 Field 041$a contains "eng"
2. Navigate to Cataloging > MARC modification templates > + New template (name "Change 008 lang")
3. Click + New action
4. Add "008" after "field(s)"
5. From dropdown "Delete", select "Update existing or add new"
6. To the input field on the right of "starting from character position", enter value 35
7. To the input field on the right of "with value", enter eng
8. From the empty dropdown below, select "if"
9. To the input field on the right of "field", enter 041, and to the right, enter a
10. From the dropdown on the right, select "matches"
11. Check "RegEx"
12. To the empty input field on the right, enter ^eng$
(the line should now say: [if] field [041] [a] [matches] m/[^eng$]/ RegEx [X])
13. Give any description you want (ex. Change 008 35-37 to eng)
14. Navigate to the record details you added in step 1
15. Click Edit > Modify record using template
16. Click "Show MARC"
17. Double check field 008 character position 35-37 is currently |||
18. Click "Close"
19. From dropdown "Select a template", select your new modification template from step 2
20. Click "Show MARC"
21. Observe field 008 character position 35-37 is now eng
22. Click "Modify selected records"
23. After job is completed, observe field 008 character positions 35-37 is now eng

Also test MARC modification templates for non-control fields in case of regression.

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:14 +00:00
Lari Taskula 3a672758c8
Bug 28915: Fix field_exists call in C4::MarcModificationTemplates
Control fields (generally, just tags below 010) do not have subfields, use data() at /home/koha/Koha/Koha/SimpleMARC.pm line 460.

To test:
1. prove t/db_dependent/MarcModificationTemplates.t
2. Observe success

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:13 +00:00
Lari Taskula fed20335a4
Bug 28915: Add test for AddModificationTemplateAction
To test:
1. prove t/db_dependent/MarcModificationTemplates.t

Observe test failing with the following error

t/db_dependent/MarcModificationTemplates.t .. 1/135 Control fields (generally, just tags below 010) do not have subfields, use data() at /home/koha/Koha/Koha/SimpleMARC.pm line 490.

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:11 +00:00
Lucas Gass bc54c59423
Bug 28915: (follow-up) Update kohatructure.sql
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:10 +00:00
Andrew Fuerste Henry c19f99daa1
Bug 28915: (follow-up) Increase field size and maxlength in template
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:09 +00:00
Andrew Fuerste Henry 6942279435
Bug 28915: (follow-up) atomicupdate to increase from_subfield size
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:08 +00:00
Andrew Fuerste Henry 433fa34c11
Bug 28915: (follow-up) Add POD
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:07 +00:00
Lari Taskula 6db0d42427
Bug 28915: Add character position to MARC modification templates view
To test new form events:
1. Navigate to Cataloging > MARC modification templates > + New template (and provide any name)
2. Click + New action
3. Add "008" after "field(s)" and click tabulator or your mouse anywhere on the screen
4. Observe the second (sub)field input field disappearing
5. From dropdown "Delete", select "Update existing or add new"
6. Observe new text "starting from character position"
7. Select any other action from the dropdown from step 5
8. Observe the text "starting from character position" disappearing (unless action is to update or add new field)
9. Observe subfield input field missing (unless action is to update or add new field)

To test MARC modification with the new feature:
1. Add a new record
2. Modify record's 008 field character position 2-3 to "06" (indicating month June)
3. Navigate to Cataloging > MARC modification templates > + New template (name "Change June to September")
4. Click + New action
5. Add "008" after "field(s)"
6. From dropdown "Delete", select "Update existing or add new"
7. To the input field on the right of "starting from character position", enter value 2
8. To the input field on the right of "with value", enter 09
9. From the empty dropdown below, select "if"
10. To the input field on the right of "field", enter 008
11. From the dropdown on the right, select "matches"
12. Check "RegEx"
13. To the empty input field on the right, enter ^[0-9][0-9]06
14. Give any description you want (ex. Change 008 June to September)
15. Navigate to the record details you added in step 1
16. Click Edit > Modify record using template
17. Click "Show MARC"
18. Double check field 008 character position 2-3 is currently 06
19. Click "Close"
20. From dropdown "Select a template", select your new modification template from step 3
21. Click "Show MARC"
22. Observe field 008 character position 2-3 is now 09
23. Click "Modify selected records"
24. After job is completed, observe field 008 character positions 2-3 are now 09

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:06 +00:00
Lari Taskula 534c54f730
Bug 28915: Add control field support to Koha::SimpleMARC::update_field()
Before this patch, control fields were not modifiable using this subroutine.

Control fields use character positions instead of subfields. When modifying
control fields, this patch makes the subroutine's parameter "subfield" to indicate
a character position. An example

Koha::SimpleMARC::update_field({
  record => $record,
  field  => '008',
  subfield => '2',
  values => [09],
});

changes field 008 starting from character position 2 to contain values "09".
This replaces char pos 2 with "0", and char pos 3 with "9".

To test:
1. prove t/SimpleMARC.t

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:05 +00:00
Saiful Amin 4c255bdde8
Bug 29587: Test mail option in SMTP servers
Edit: Added CSRF protection.

This patch adds a "Send test email" action to the SMTP servers
administration page. It sends a test message to the address in
the KohaAdminEmailAddress system preference and reports success
or failure to the user.

Test plan:
1. Add a valid SMTP server in Administration → SMTP servers.
2. Set KohaAdminEmailAddress to a valid email address.
3. Apply the patch.
4. Click "Send test email" in the Actions column.
5. Confirm that Koha reports "Test email sent successfully".
6. Edit the SMTP server and change the domain to an invalid one.
7. Click "Send test email" again.
8. Confirm that Koha reports failure.
9. Sign off.

Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:04 +00:00
Emmi Takkinen 94a6a93b64
Bug 41829: Set correct id to copied Tag editor button
When one copies MARC field, Tag editor button on fields
containing value builder plugin have same id as original
MARC field. This patch sets correct id to these buttons.

To reproduce:
1. Find MARC framework which has value builder plugin
   attached to one of its fields.
2. Navigate to Cataloging -> New record.
3. Navigate to MARC field with plugin attached to it.
4. Inspect fields Tag editor button with browsers developer
   tools.
   -> Check the id of Tag editor button.
5. Copy the field and check id of copied Tag editor button.
   -> Notice that it has the same id as in original field.
6. Apply this patch.
7. Referesh the page, navigate back to field and copy it.
8. Inspect Tag editors id.
   -> Id should now be same as in other copied fields.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:03 +00:00
Matt Blenkinsop 456fa3809a
Bug 41605: Fix incorrect default key
Test plan:
No behavioural change but it is an inconsistency in the code
1) Look in base-resource.js at the newResource computed method
2) Confirm that the code in the first conditional block is looking for 'hasOwnProperty("defaultValue")'
3) Check the new patch and confirm that the code has been changed from 'value: false' to 'defaultValue: false' to match this

There is no behaviour change as boolean fields default to false within the framework but we should remove this inconsistency

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 15:24:02 +00:00
Nick Clemens 0767f26643
Bug 42245: Prevent preselecting sort values for guarantor search
av-build-dropbox.inc takes a 'default' param

When building the patron we are setting this to the value of sort1, then sort2
This seems to remain cached, and when we build the guarantor search the sort2 value is used as the default
This patch ensures we pass a blank default to the modal

To recreate:
1 - Define 2 values for 'BSort1' in Administration -> Authorised values
2 - Define 2 values for 'BSort2' in Administration -> Authorised values
3 - Find a borrower who is eligible to have a guarantor
4 - Edit patron and assign values to sort 1 and 2 for this borrower and save
5 - Edit patron again and click 'Add guarantor'
6 - Note that sort 2 is populated
7 - Apply patch
8 - Reload borrower
9 - Confirm sort1 and 2 are empty

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 12:54:35 +00:00
Laura_Escamilla b2780e6136
Bug 42233: Add accessible text to empty purchase suggestions table header
The first header cell in the OPAC purchase suggestions table contains
only an icon, leaving the <th> without accessible text. This causes
the column header to be announced unclearly by assistive technology
and can be flagged by accessibility testing as an empty table header.

Add visually hidden text to the header so the column has a meaningful
accessible name while preserving the current visual display.

Test plan:
1. Log in to the OPAC
2. Make a purchase suggestion
3. In the suggestions table, the first <th> contains no text
4. Apply the patch
5. Repeat steps 1-2
6. Inspect the suggestions table header
7. Confirm the first <th> now contains visually hidden text describing
   the checkbox selection column
8. Confirm the info icon still appears and its tooltip still works
9. Run an accessibility checker or inspect the DOM and confirm the
   empty table header issue is resolved

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 12:54:34 +00:00
Laura_Escamilla fee16fa179
Bug 42299: Add accessible label to star rating select in OPAC
To test:
1. Set  OpacStarRatings so it shows on the OPAC detail page
2. Notice star ratings has no associated label element
3. Apply the patch and refresh the OPAC detail page you were viewing.
4. Notice that the star ratings now have an accessible label.
5. Sign off and have a great day. :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 12:54:33 +00:00
Laura_Escamilla d7f59e7adc
Bug 42232: Add missing legend to action fieldset in OPAC
To test:
1. Log on to the OPAC and go to "Messaging" page.
2. The fieldset that contains the Save/Cancel button is missing a legend.
3. Apply the patch and refresh the "Messaging" page.
4. Notice that the fieldset now contains an accessible legend.
5. Sign off and have a great day!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 12:54:32 +00:00
Jonathan Druart 6a22a2a0fe
Bug 41992: Do not restore pagination offset
When "Save configuration state on page change" is enabled, the page
number is kept in localStorage and restored.

To prevent that we can set the attribute "start" of the state to 0.

Test plan:
Go on a page with several pages, go to another page than the first one
Go the another view and get back
Without this patch the last visited page was preserved when returning to the previous view
With this patch applied the view now defaults to the first page

Note that this does not affect shareable links.

Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 12:54:31 +00:00
Owen Leonard a7591ea3c6
Bug 42229: (follow-up) Restore card number <label> when necessary
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 12:54:29 +00:00
Owen Leonard e046b14a57
Bug 42229: (follow-up) Markup correction
The "Street type" fields had double "name" attributes.

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 12:54:28 +00:00
Laura_Escamilla 8073ef26b7
Bug 42229: Avoid using form labels for static values in opac-memberentry.tt
The OPAC personal details page uses <label> elements in some places
where no visible form control is present. In these cases, the content
is display-only and the label is not associated with an interactive
field.

Replace those labels with non-form markup for static values while
preserving existing labels for actual form controls.

Test plan:
1. Log in to the OPAC and go to the "Personal details" page
2. Inspect display-only values such as Username and Expiration date
3. Confirm <label> elements are used for content that is not part of a
   form control
4. Apply the patch
5. Log in to the OPAC and go to the "Personal details" page
6. Confirm display-only values such as Username and Expiration date no
   longer use <label> elements
7. Confirm other static display-only values updated by the patch also no
   longer use <label> elements
8. Confirm actual form controls, such as text inputs and selects, still
   have proper associated labels
9. Run an accessibility checker and confirm the related label misuse
   issues are resolved
10. Sign off and have a great day!

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 12:54:27 +00:00
Johanna Räisä 86b0fae756
Bug 42395: Fix missing translations on load_patron_holds_table
This patch adds missing translations for the load_patron_holds_table function.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 12:54:26 +00:00
Pedro Amorim 1b93c7f6bd
Bug 42485: (RM follow-up): Tidy
prove xt/perltidy.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-15 12:54:23 +00:00
David Cook 4648f79f8d
Bug 42612: Change button colours in manage-marc-import.pl
This patches changes the button colours in manage-marc-import.pl
so that "Import this batch into the catalog" is more prominent,
and "Apply different matching rules" is less prominent.

Test plan:
0. Apply the patch
1. Go to a bib record like
http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=29
2. Click "Save" and choose "MARCXML"
3. Go to http://localhost:8081/cgi-bin/koha/tools/stage-marc-import.pl
4. Click "Upload file"
5. Choose "MARCXML" for the "Format:"
6. Change "Check for embedded item record data" to "No"
7. Click "Stage for import"
8. Wait for the staging to complete. This will be show when the status
bar hits "100% Finished" and a "View batch" link appears.
9. Click "View batch"
10. Note that "Import this batch into the catalog" is a prominent yellow
button with black text
11. Note that "Apply different matching rules" is a less prominent dark grey
button with white text

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:19 +00:00
Artur Mickiewicz 237ecdc4bc
Bug 41987: Remove duplicate searchresult-breeding id
The searchresult-breeding id was present on both the wrapper div and
the "no results" alert element, causing duplicate id usage in the DOM.

This patch ensures the id is only used once on the parent container
and keeps the anchor link functional.

Signed-off-by: Artur Mickiewicz <davno_pora@mail.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:18 +00:00
Laura_Escamilla 1683524683
Bug 42234: Add text to checkout history cover image table header
1. Enable at least one cover image service, such as GoogleJackets or
   OPACAmazonCoverImages.
2. Log in to the OPAC as a patron with checkout history.
3. Go to your account > Checkout history.
4. Inspect the checkout history table header.
5. Confirm the cover image column header contains "Cover image".
6. Confirm the table still displays correctly.
7. Confirm sorting/export behavior is unchanged for that column.
8. Sign off and have a wonderful day! :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:17 +00:00
Martin Renvoize 081dbc1422
Bug 42485: (QA follow-up) Add explicit JSON::PP import and test coverage
Add explicit `use JSON::PP ()` to Elasticsearch.pm and the test file
to make the dependency on JSON::PP::false visible rather than relying
on YAML::XS loading it as a side effect.

Extend the get_elasticsearch_mappings() test subtest to assert that
date_detection and numeric_detection are disabled at the mapping root.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:16 +00:00
Tomás Cohen Arazi 9684fcd5c3
Bug 42485: Disable date and numeric detection on ES mappings
When ElasticsearchMARCFormat is set to ARRAY, the marc_data_array
field uses dynamic:true mapping. Elasticsearch auto-detects field
types based on the first document indexed. If a MARC subfield value
looks like a date (e.g. 2024-01-15 in 952$e), ES locks that
subfield to type date. Subsequent records with non-date values in
the same subfield fail to index with a document_parsing_exception.

This patch sets date_detection and numeric_detection to false at
the mapping root level, so ES treats all dynamically mapped fields
as text instead of guessing types.

Test plan:
1. $ ktd --search-engine es8 --name test-es --proxy up -d
   $ ktd --name test-es --wait-ready 180
   $ ktd --name test-es --shell
2. k$ koha-mysql kohadev -e "UPDATE systempreferences
     SET value='ARRAY'
     WHERE variable='ElasticsearchMARCFormat';"
   k$ echo 'flush_all' | nc -q1 memcached 11211
   k$ koha-mysql kohadev -e "DELETE FROM biblio_metadata;
     DELETE FROM items; DELETE FROM biblioitems;
     DELETE FROM biblio;"
   k$ rebuild_elasticsearch.pl -d -r --biblios
3. Add a record with date-like 952$e and index it:
   k$ koha-shell kohadev -p -c 'perl -MC4::Biblio
     -MMARC::Record -MMARC::Field -MKoha::Item -e "
     my \$r = MARC::Record->new();
     \$r->append_fields(
       MARC::Field->new(q{245},q{1},q{0},a=>q{Record one}),
       MARC::Field->new(q{942},q{ },q{ },c=>q{BK}));
     my (\$bn) = C4::Biblio::AddBiblio(\$r, q{});
     Koha::Item->new({biblionumber=>\$bn,
       biblioitemnumber=>\$bn, homebranch=>q{CPL},
       holdingbranch=>q{CPL}, itype=>q{BK},
       booksellerid=>q{2024-01-15}})->store;
     print qq{Added biblio \$bn\n};"'
   k$ rebuild_elasticsearch.pl --biblios -bn <biblionumber>
4. Check the mapping for 952$e:
   k$ curl -s es:9200/koha_kohadev_biblios/_mapping/field/\
     marc_data_array.fields.952.subfields.e
=> FAIL: type is "date" (should be "text")
5. Add a record with non-date 952$e and index it:
   k$ koha-shell kohadev -p -c 'perl -MC4::Biblio
     -MMARC::Record -MMARC::Field -MKoha::Item -e "
     my \$r = MARC::Record->new();
     \$r->append_fields(
       MARC::Field->new(q{245},q{1},q{0},a=>q{Record two}),
       MARC::Field->new(q{942},q{ },q{ },c=>q{BK}));
     my (\$bn) = C4::Biblio::AddBiblio(\$r, q{});
     Koha::Item->new({biblionumber=>\$bn,
       biblioitemnumber=>\$bn, homebranch=>q{CPL},
       holdingbranch=>q{CPL}, itype=>q{BK},
       booksellerid=>q{My Vendor Name}})->store;
     print qq{Added biblio \$bn\n};"'
   k$ rebuild_elasticsearch.pl --biblios -bn <biblionumber> -v -v -v
=> FAIL: document_parsing_exception - failed to parse date field
   [My Vendor Name]
6. Apply this patch, repeat from step 2
7. Repeat step 4:
=> SUCCESS: type is "text"
8. Repeat step 5:
=> SUCCESS: No indexing errors
9. Sign off :-D

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:15 +00:00
Jonathan Druart e5aa1bf86d
Bug 38122: Add tests
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:14 +00:00
Jonathan Druart 9e4f896078
Bug 38122: Add space before the Group/Ungroup links
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:13 +00:00
Jonathan Druart 191b53278e
Bug 38122: Keep filters visibility
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:12 +00:00
Ayoub Glizi-Vicioso 0e0607ce80
Bug 38122: Tidy and Minor cleanup in catalogue_detail.inc (replace Template Toolkit leftovers)
1- perl `misc/devel/tidy.pl` executed.
2- Fixes xt/tt_kohaTable_tidy.t failure triggered by catalogue_detail.inc.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:11 +00:00
Jonathan Druart fe876c795d
Bug 38122: Add the ability to group holdings by status
This is a quite ugly solution, but that may answer end-user needs.

It add the option the group items by status on the bibliographic record
detail page.

However it will have VERY bad impact on performances for record with a
lot of items, as we are going to determine the status of each items of
the record.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:10 +00:00
Owen Leonard 89da78b3b9
Bug 42684: Capitalization: No Barcode
This patch corrects the capitalization of the phrase "No Barcode" and
several other instances of incorrect capitalization.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-12 16:24:07 +00:00
David Cook 18dad35de4
Bug 42361: Fix SQL injection in catalogue_out.pl
This change removes an SQL injection vulnerability from catalogue_out.pl

Test plan:
0. Apply the patch
1. Go to koha-conf.xml and change the following:
<strict_sql_modes>1</strict_sql_modes>
to:
<strict_sql_modes>0</strict_sql_modes>
2. sudo koha-plack --restart kohadev
3. Go to /cgi-bin/koha/reports/catalogue_out.pl
4. Try out the various options and note no errors occur

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-09 14:08:08 +00:00
Jonathan Druart 3c67759548
Bug 42802: Update kohastructure.sql
Test plan:
root@kohadevbox:koha$ perl /kohadevbox/misc4dev/run_tests.pl --run-db-compare-only --compare-with v24.05.00
should pass with this patch

Without this patch you get:
Running [diff /tmp/current_db.sql /tmp/upgraded_db.sql && diff -q /tmp/current_db.sql /tmp/upgraded_db.sql || { echo "ERROR - DB structures are not identical" && exit 1; }]...
7207c7207
< /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-09 14:08:07 +00:00
Pedro Amorim 28c94aeb15
Bug 42783: Fix ManageMarcImport_spec.ts
If import_batch_profiles is slow to respond, it'll trigger select.change() in stage-marc-import.tt and change the format to ISO2709 before the form is submitted, causing the error.

cypress run --spec t/cypress/integration/Tools/ManageMarcImport_spec.ts

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-08 14:16:25 +00:00
Pedro Amorim 8efbd99ef8
Bug 42764: (RM Follow-up): typo fix dbic
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-08 14:16:24 +00:00
Pedro Amorim bad922332b
Bug 42764: (QA Follow-up): Fix DB update mismatch
perl /kohadevbox/misc4dev/run_tests.pl --run-db-compare-only --compare-with v24.05.00

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-08 14:16:16 +00:00
Janusz Kaczmarek 34d708b930
Bug 42555: (Bug 25314 follow-up) Destroyed labels for customized facets
After applying Bug 25314, customized facets generated from
mappings.yaml are displayed with the technical type_id instead of the
proper label.

Test plan:
==========
1. In KTD with Elasticsearch enabled, add the following to
   admin/searchengine/elasticsearch/mappings.yaml

  su-gen:
    facet_order: 33
    label: Genre/Form
    mappings:
    - facet: 1
      marc_field: 655a
      marc_type: marc21
      sort: 1
      suggestible: ''
    opac: 1
    staff_client: 1
    type: ''

   Reindex with: koha-elasticsearch --rebuild -r -d -b kohadev
2. In the OPAC, at the bottom of the facets area, you should see a
   facet labeled su-gen_id instead of Genre/Form.
3. Apply the patch ; restart_all.
4. The facet should now display the correct label: Genre/Form.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:15 +00:00
Lucas Gass c183beefac
Bug 42757: Use default suggestor or fallback to librarian
To test:
1. Find a patron
2. From the patron account click the "Purchase suggestions" button
3. Make a suggestion
4. That suggestion is not associated with the patron from step 1
5. APPLY PATCH and restart_all
6. Repeart steps 1 -3. The purchase suggestion suggesedby should be the patron from step 1
7. Go  Acquisitions -> Suggestions -> Add suggestion
8. Make sure when you do it this way the suggestedby is set to the logged-in-user by default

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:14 +00:00
Lucas Gass 391d9f461c
Bug 42750: Add redirect params back and exit after redirect
To test:
1. Find a patron
2. From the patron account click the "Purchase suggestions" button
3. Make a suggestion
4. Your are redirected to suggestion/suggestion.pl
5. APPLY PATCH, restart_all
6. Try again, this time you should be properly redirected back to the patron account: ( members/purchase-suggestions.pl?borrowernumber=X )

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:13 +00:00
Emily Lamancusa db044d8552
Bug 42701: Fix editable class on additem.pl
When editing the items on a biblio record, you should be able to select
an item to edit by clicking anywhere on the item row. This functionality
was lost because the "editable" class was not getting added correctly to
the table row. This patch fixes that issue.

To test:
1. Find a biblio record that has at least one item
2. Click on Edit > Manage items
3. In the table that lists editable items, click on any cell of any item
   (other than the Actions button)
--> Nothing happens; the buttons to "Edit item" and "Delete item" do not
    appear as expected
4. Apply patch and refresh the page
5. Repeat step 3
--> The buttons to "Edit item" and "Delete item" appear

Signed-off-by: Barbara Johnson <barbara.johnson@bedfordtx.gov>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:12 +00:00
Lucas Gass 86b2eb9a3a
Bug 42740: Fix suggestion status when editing
To test:
1. Make a purchase suggestion.
2. Set the status to something, anything.
3. Edit that suggestion, notice that the "Status:" is set to 'No Status'
4. Now when you save, they actual status is lost.
5. APPLY PATCH, restart_all
6. Try steps 1 - 3. This time at step 3 the suggestion status should be properly set.
7. Save the suggestion, make sure it saves with the correct status.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:11 +00:00
Baptiste Wojtkowski d1476c8184
Bug 41998: Fix redirect_with_params
Test plan (main only):
1 - Create a suggestion
2 - Delete the suggestion -> error
3 - Apply patch
4 - Repeat 1&2

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:10 +00:00
Eric Phetteplace 9c994035fe
Bug 42723: Purchase suggestion 500 when EmailPurchaseSuggestions is set to BranchEmailAddress
Testing plan:
- Set EmailPurchaseSuggestions to BranchEmailAddress
- Go to create suggestion on the staff side /suggestion/suggestion.pl?op=add_form
- Create a suggestion with Library: Any
- 500 error: inbound_email_address is not a method of undef
- Apply patch & repeat
- Suggestion created

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:09 +00:00
Julian Maurice d6f1bdbe63
Bug 42741: Fix plural form in lists
Test plan:
1. Go to Lists
2. Create three lists called:
   - Test 0 items
   - Test 1 item
   - Test 2 items
3. Add one item in the "Test 1 item" list and add two items in the "Test
   2 items" list
4. Go to lists and view the list of lists
   --> In the Contents column, "Test 0 items" should be "0 items"
   --> "Test 1 item" should be "1 item"
   --> "Test 2 items" should be "2 items"

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:08 +00:00
Tomás Cohen Arazi 8798f1a7a7
Bug 42739: Add CSRF-TOKEN header to OPAC ratings.js fetch call
The ratings.js file uses raw fetch() to POST to the REST API without
including the CSRF-TOKEN header. This will fail once CSRF enforcement
is enabled on the REST API.

Test plan:
1. Apply patch
2. Enable star ratings in OPAC (StarRatings syspref)
3. Log in to the OPAC
4. Rate a biblio using the star widget
=> SUCCESS: Rating is saved without errors
5. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:07 +00:00
Nick Clemens 0c5a53a8d0
Bug 42659: Use biblio-title.inc for displaying title at checkin
Currently at checking we display the number and parts all grouped,this is different than
other places in Koha, we should use the same include here

To test:
1 - Edit a record and set the title like:
    245 1 4 ‡aThe passion according to G.H. ‡bsubtitle ‡nnumber1 ‡ppart1 ‡nnumber2 ‡ppart2 /‡cClarice Lispector ; translation by Ronald W. Sousa.
2 - Enable syspref 'ShowAllCheckins'
3 - Checkin an item from that record, display is like:
    The passion according to G.H. subtitle number1 number2 part1 part2 /
4 - Apply patch
5 - Check in again
6 - Display is now:
    The passion according to G.H. subtitle number1 part1 number2 part2 /
7 - Sign off!

Signed-off-by: Eric Garcia <cubingguy714@gmail.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:06 +00:00
Lucas Gass f2735f73ef
Bug 42710: Set library to logged in library for new suggestions
To test:
1.  Acquisitions -> Suggestions ->Add suggestion
2.  Notice the under Acquisition information the library is set to Any
3.  APPLY PATCH
4.  Try again, library should be set to the logged in library.
5.  Make a suggestion and set it to some branch other then the logged in branch
6   Edit the suggestion from step 5, ensure that the "Library:" is properly set

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:05 +00:00
David Nind af711ae7c0
Bug 42454: Terminology: Use "and" instead of "&" for curbside pickups tab
The "Staged & ready (0)" tab for curbside pickups (Circulation > Holds
and bookings > Curbside pickup) should use "and" instead of "&".

See terminology guidelines for "& (ampersand)" - Only use "&" when part
of a proper noun or a common abbreviation,... Otherwise, use "and":
https://wiki.koha-community.org/wiki/Terminology#characters

Test plan:
1. Enable the CurbsidePickup system preference.
2. Configure curbside pickup for Centerville:
   - Go to Administration > Patrons and circulation > Curbside pickup
   - Set values:
     . Enable: selected
     . Pickup interval: 5
     . Maximum patrons per interval: 1
     . Patron-scheduled pickup: selected
     . Enable for waiting holds only: selected
     . Curbside pickup hours: add from and to hours for some days
     . Click Save configuration
3. Go to Circulation > Holds and bookings > Curbside pickup.
4. Note that there is an '&' in the "Staged & ready (0)" tab title.
5. Apply the patch.
6. Refresh the page.
7. Note that and is spelt in full "Stage and ready (0)".

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:04 +00:00
Owen Leonard c57cb6c6d5
Bug 42498: Fix invalid aria attribute in OPAC search results pagination
This patch corrects the aria-current attribute in OPAC search results
pagination. There should be no aria-current attribute on the list item,
and the aria-current value on the link should be "page."

To test, apply the patch and perform a catalog search in the OPAC which
will return multiple pages of results.

View the page source or use the developer console to inspect the HTML of
the active pagination link. It should have an aria-current label with
the value "page." The containing list item should not have an
aria-current label.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:03 +00:00
Owen Leonard b71b2bbc98
Bug 42691: Typo: automaticaly
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:02 +00:00
Owen Leonard 813c292957
Bug 42692: Typo: regarless
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:01 +00:00
Owen Leonard b021ad4a2e
Bug 42518: Capitalization: Refund Payout Receipt
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:26:00 +00:00
Owen Leonard 94e9d14151
Bug 42707: Malformed Bootstrap dropdown in reports output
This patch updates the SQL report results markup in order to make sure
Bootstrap dropdowns are formatted correctly.

To test, apply the patch and locate or create a report which returns
patron card number, e.g.

SELECT cardnumber, borrowernumber FROM borrowers LIMIT 10

In the results, test the menus triggered when you click a card number or
a borrowernumber. The menu in the cardnumber column should be
formatted like the menu for borrowernumber.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:59 +00:00
David Nind 5036ff8edd
Bug 42695: (follow-up) Format paragraph
Moves the explanation sentence into its own paragraph.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:58 +00:00
Owen Leonard 7dce6c266f
Bug 42695: Minor language and markup corrections to reports error messages
This patch makes a vew minor corrections to guided_reports_start.tt.

To test apply the patch and go to Reports.

- Try to create an SQL report which uses UPDATE, e.g.
  UPDATE items SET barcode = 1 WHERE itemnumber = 1
- Confirm that the text of the error message reads well and is formatted
  well.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:57 +00:00
Jonathan Druart 833937d6d2
Bug 42708: (bug 41566 follow-up) Restore display of itemtype images
> - [% UNLESS noItemTypeImages %]
> + if (prefs.noItemTypeImages){

This is wrong!

Test plan:
Go to /cgi-bin/koha/catalogue/detail.pl?biblionumber=4
Notice that with this patch the "Book" icon is displayed
Turn off noItemTypeImages and reload
=> It's now hidden!

Without this patch the behavior is reversed

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:56 +00:00
Andrii Nugged 2970d72880
Bug 42702: Fix malformed cancel links on parcel.pl
The pending orders table builds the two cancel order links with the
link text concatenated immediately after the invoice_id value. This
leaves the href attribute unclosed and makes the browser absorb the
link text and following escaped HTML into the href.

Add the missing separator after invoice_id for both cancel link builders.

Test plan:
1. Create a closed acquisition basket with a pending order.
2. Open an invoice receive page for the vendor.
3. Inspect the Cancel order link in the pending orders table.
4. Confirm the href ends after invoiceid and the link text is outside the href.

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:55 +00:00
Jonathan Druart 56075a587c
Bug 42130: ERM - Index biblio when local title is created/updated
The indexation step has been removed by
  commit d2820f57d6
  Bug 34788: (QA follow-up)

Koha/ERM/EHoldings/Title.pm
-            C4::Biblio::ModBiblio( $record, $self->biblio_id, '' );
+            C4::Biblio::ModBiblio( $record, $self->biblio_id, '', { skip_record_index => 1 } );

From the commit message:
2) The background job now uses skip_record_index to avoid queuing indexing jobs for every new biblio and instead queues one job at the end

it's then missing when we add or edit a local title.

Test plan:
1 - Enable ERMModule
2 - Go to ERM
3 - eHoldings->Titles
4 - New title
5 - Populate the form
6 - Check the create bibliographic record box
7 - Save
=> Confirm that the biblio is now indexed
8. Edit the title, modify a value, tick the box, save
=> Confirm that the biblio is now indexed with the new value

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:54 +00:00
Kevin Carnes 2a655af089
Bug 42178: Fix close button for remove from bundle
The close button for remove from bundle has not type, so it submits the form.

This patch adds a button type.

Test plan:
1. Apply bug 42177 or add a local use preference for bundlesEnabled set to 1.
2. Edit a record.
3. Modify the leader so that it has a collection bibliographic level.
4. Ensure that is has at least one item.
5. Click Manage bundle next to the item.
6. Click Remove from bundle.
7. Click the Close button.
8. Observe that the form is submitted.
9. Apply the patch.
10. Repeat steps 5-7.
11. Observe that the form is not submitted.
12. Sign off.

Sponsored-by: Lund University Library
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:53 +00:00
Maryse Simard 0023371a4e
Bug 42193: Fix the "Suspend Hold" modal in the OPAC
In the OPAC, the "Suspend Hold" modal sometimes shows up as (and acts like) a "Resume Hold" modal. The reason for this is a left over "HOLD.suspend" in the template. "HOLD" should not be used in this template and is set as a side effect of the processing of "holds-table.inc".

Since there is a separate form that exist for the "Resume" action and the modal is only needed for suspension, this patch simply removes the inacurate conditional in the modal definition.

To test :
1. Have at least 2 holds.
2. In the holds table in the OPAC, make sure all holds are active and have a "Suspend" link.
3. Click the "Suspend" link on the newest hold (with the highest reserve_id).
    => The modal is titled "Suspend hold" and submitting it correctly suspends the hold.
4. Click the "Suspend" link on any of the other hold.
    => The modal is titled "Resume hold" and submitting it doesn't suspends the hold.
5. Apply patch
6. Repeat step 4.
    => The modal is titled "Suspend hold" and submitting it correctly suspends the hold.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:52 +00:00
Julian Maurice 9898bca5d0
Bug 42194: Fix redirect after sharing a report to Mana
Bug 34478 removed a '&' by mistake in the redirect URL

Test plan:
1. Setup a local instance of Mana
   (https://gitlab.com/koha-community/koha-mana) with disabled email
   validation (skip_email_validation in config.yml)
2. Connect Koha to this local instance (mana_config in $KOHA_CONF)
3. From Koha Admin interface, create a Mana security token
4. Share a report to Mana
5. You should be redirected to the list of reports with a message saying
   the report was saved successfully

Signed-off-by: Emmanuel Bétemps <e.betemps@gmail.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:51 +00:00
Martin Renvoize 29b71a0749
Bug 32938: Add ORDRSP message display to basket page
This patch extends the EDIFACT message display functionality on the basket page
to include ORDRSP (Order Response) messages alongside existing QUOTE and ORDERS
messages.

Features added:
- ORDRSP message collection and filtering
- Smart button display logic:
  * Single button for one message type
  * Dropdown for multiple message types (now includes ORDRSP)
- ORDRSP-specific UI elements:
  * Reply icon (fa-reply) indicating response nature
  * "View order response" labeling
  * Integration with existing modal and highlighting system

The ORDRSP messages correctly link to baskets since they are vendor responses
to the original order, following the standard EDI workflow:
ORDERS → ORDRSP → INVOICE

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:50 +00:00
Martin Renvoize 8897fde297
Bug 32938: Fix ORDRSP message linking to point to baskets instead of invoices
ORDRSP (Order Response) messages are responses to ORDERS messages and should
link to the related basket, not to invoices. This follows the standard EDI
workflow:
1. ORDERS message sent to vendor
2. ORDRSP message received from vendor (order response/confirmation)
3. INVOICE message received later when items shipped

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:49 +00:00
Pedro Amorim 370de0a3aa
Bug 42764: DBRev 26.06.00.001
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:48 +00:00
Pedro Amorim 50e1739f87
Bug 42764: dbic
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:47 +00:00
Pedro Amorim 93593ce70c
Bug 42764: (QA follow-up): Update DB schema
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:46 +00:00
Pedro Amorim dcd04a26ca
Bug 42764: (QA follow-up): Atomicupate: Fix typo in SQL table comment
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-05 14:25:43 +00:00
Martin Renvoize 2dc91dcd38
Bug 41705: (follow-up) Fix for new kohaTable requirements
This bug sat in the queue too long and thus there's coding standard
changes to resolve.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-06-04 18:29:08 +01:00
Martin Renvoize 3ed47b65e8
Bug 42764: Use inline codespell:ignore for SAN-Ouest Provence in templates
Rather than excluding the entire about.tt files from codespell scanning,
annotate only the specific lines containing the proper noun "Provence"
(SAN-Ouest Provence, a French library co-operative and Koha sponsor) with
an inline TT comment that codespell recognises as a per-line ignore.

This lets codespell continue to check the rest of each file for genuine
spelling mistakes, while still catching accidental "Provence" for
"Province" in any other template not carrying this annotation.

Replaces the file-level exceptions added to Koha::Devel::Files in
favour of the more surgical inline approach.

Test plan:
1. Apply patch
2. Verify "Provence" on the annotated lines produces no codespell output:
   codespell -d --ignore-words .codespell-ignore \
     koha-tmpl/intranet-tmpl/prog/en/modules/about.tt
   => no output
3. Verify the rest of about.tt is still checked by introducing a
   deliberate typo elsewhere in the file and confirming codespell flags it
4. prove xt/author/codespell.t
=> All tests pass

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-06-04 10:59:21 +01:00
Martin Renvoize cc7c1aa157
Bug 42764: Fix false positives caused by mixed filetypes in CI failure store
The ci-results repo stores all failures from a single test run as one
flat JSON array, regardless of whether they came from the pl, tt, or js
filetype pass. On the next incremental run, get_files_to_test('pl') reads
that entire list -- including any .tt failures -- and calls
remove_exceptions with the 'pl' context. Since .tt files only appear in
the tt exception list, they survive the filter and get codespell'd in the
pl pass where nothing is configured to exclude them.

The fix filters stored failures by filetype before applying exceptions,
so each pass only re-checks failures that belong to its own type.

Test plan:
1. Apply all patches
2. Add a file to the tt codespell exceptions in Koha::Devel::Files that
   contains a word codespell would flag
3. Introduce a deliberate failure in another file so the ci-results repo
   records the excepted .tt file as a previous failure
4. On the next incremental run, confirm the excepted .tt file is not
   re-tested in the pl or js pass
5. prove xt/author/codespell.t
=> Tests pass; no false positives from cross-filetype re-checks

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-06-04 10:59:17 +01:00
Martin Renvoize 84536a08c9
Bug 42764: Include filename in codespell test output
Without a test comment, failures appear as bare 'not ok N' lines with
no indication of which file failed or what codespell found.

Passing the filename as the third argument to is() means:
- Every passing test shows: ok N - path/to/file
- Every failing test shows: not ok N - path/to/file

Test plan:
1. Apply patch
2. Introduce a deliberate spelling error in any .pl file
3. prove xt/author/codespell.t
=> Failing line shows the filename and the codespell finding inline,
   without needing to dig into full console logs

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-06-04 10:59:10 +01:00
Martin Renvoize 5b2293c778
Bug 42764: Fix false positives caused by POD letter-code formatting
Two patterns in the codebase produce spurious codespell hits because
codespell tokenises word fragments out of formatted letter codes:

- B<P>rovider in C4/Auth_with_shibboleth.pm produces 'rovider'
- T(ransit), W(aiting), F(inished), P(rocessing) in C4/Reserves.pm
  produce 'ransit', 'aiting', 'inished', 'rocessing'

For the shibboleth POD line the bold-letter markup cannot be simplified
without losing the formatting intent, so a per-line # codespell:ignore
comment is the right suppression.

For the Reserves.pm status table the fix is made at source: the notation
is changed from X(word) to X (Word) so codespell sees the full English
word and no fragment is produced.

Because the per-line comment on the shibboleth line now suppresses
'rovider' directly, the global .codespell-ignore entry for that fragment
is removed as redundant.

Test plan:
1. Apply patch
2. prove xt/author/codespell.t
=> No new codespell failures

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 16:09:05 +00:00
Martin Renvoize 04809de849
Bug 42764: Fix spelling errors across the codebase
Correct genuine spelling errors identified by the codespell 2.4.x
full scan after the KTD container OS upgrade. Changes are in
comments, POD, strings, and template text across 55 files.

"SAN Ouest Provence" (the French library co-operative and Koha
sponsor) is preserved as a proper noun. Perl copyright lines
receive an inline # codespell:ignore annotation; about.tt files
(contributor attribution pages) are added to the Koha::Devel::Files
codespell exception list so that future uses of "Provence" as a
typo for "Province" are still caught in all other files.

Notable user-visible fixes:
- "submiting" -> "submitting" in authority and bibliographic merge screens
- "deleteable" -> "deletable" in patron exception messages
- "Acquistions" -> "Acquisitions" in reports dictionary

Test plan:
1. Apply patches
2. In the staff interface, go to Cataloguing > Merge records and
   confirm the submit button reads "Submitting" (not "Submiting")
3. In the staff interface, go to Authorities > Merge and confirm
   the same correction
4. Attempt to delete a patron with outstanding issues and confirm
   the error message reads "deletable" (not "deleteable")
5. prove xt/author/codespell.t
=> All tests pass

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 16:09:04 +00:00
Martin Renvoize 7538ee5cf3
Bug 42764: Update .codespell-ignore for valid technical terms
Add words to .codespell-ignore that codespell 2.4.x flags but are
valid technical terms, domain vocabulary, or proper names:

- checkin: core library circulation term (check-in as noun/verb)
- plack: Perl PSGI middleware framework (proper software name)
- requestor: COUNTER 5 standard term used in ERM usage statistics
- laf: EDIFACT 3-letter segment code
- vai/lamba: Unicode script and language names in character set data
- aas: acronym used in Cypress test support files
- carmel: contributor name in about.tt
- prfile: legacy variable abbreviation in C4/Creators/PDF.pm

Test plan:
1. Apply patch
2. prove xt/author/codespell.t
=> Test passes (or only genuine spelling errors remain)

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 16:09:03 +00:00
Owen Leonard 8fc481e57a
Bug 42441: Remove event attributes from authority merge template
This patch moves a couple of "onclick" event attributes from the
authority merge template, moving the event handling to the JS block.

This patch also fixes the behavior that I assume the click handlers are
meant to have, changing the framework selection. A mismatch of IDs in
the code meant that clicking the radio buttons didn't do anything.

To test, apply the patch and go to Authorities.

- Perform a search for authority records which will return multiple
  results, including records with different heading types (e.g. Personal
  Name, Topical Term, etc.)
- In the "Actions" menu for one of the search results, click "Merge"
- Do the same with another result which has a different heading type.
- On the "Merging records" page you should see a frameowrk dropdown.
- When you click the radio buttons next to your two records to merge the
  framework selection should change to match your selection.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>

Amended-by: Jonathan Druart
Tidy

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 16:09:00 +00:00
Owen Leonard a3569fe0c2
Bug 42154: Bug 38714 hid the "New match check" link in record matching rules
This patch corrects the logic used to determine whether to show the "New
match check" link. I mistakenly changed it to check for the matchpoints
variable instead of the matchchecks variable.

To test, apply the patch and go to Administration -> Record matching
rules.

- Click "New record matching rule" in the toolbar.
- Scroll down to the "Required match checks" section.
- You should see a "New match check" link alongside a "Remove this match
  check" link.
- Go back to the record matching rules main page.
- If necessary, create a rule which doesn't have any match checks.
- In the "Required match checks" section of the edit form you should see
  a "New match check" link, previously hidden by Bug 388714.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:18 +00:00
Marcel de Rooy 6aa37a8430
Bug 42084: (QA follow-up) Simplify
Since this is an intranet svc script (called in intranet js), there
is no need to check the session.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:17 +00:00
Hammat Wele cb058d68fd
Bug 42084: Incorrect interface shown in log viewer for system preference changes
The log viewer displays the OPAC interface for system preference changes. Since system preferences cannot be modified from the OPAC, the interface displayed in the logs appears to be incorrect.

To reproduce:

1- Disables plack
    1- sudo koha-plack --disable kohadev
    2- sudo koha-plack --stop kohadev
    3- sudo service apache2 restart
2- Change a system preference from the staff interface.
    1- Go to Administration -> System preferences.
    2- Search for a preference such as AcqCreateItem (any system preference will reproduce the issue).
    3- Change the value of the preference.
    4- Click Save all Acquisitions preferences (or save the preference).
3- Go to the log viewer:
    1- Navigate to Tools -> Log viewer.
    2- Fill the form as follows:
        Librarian: leave empty
        Modules: uncheck everything except System preferences
        Actions: keep All actions selected
        Object: leave empty
        Info: leave empty
        Interface: keep All interfaces
        Display from: choose yesterday
        Display to: leave empty
    3- Click Submit.
        ==>The log entry shows the OPAC interface.
4- Apply the patch
5- Repeat step 1,2,3
    ==>The log entry shows the Staff interface.

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:16 +00:00
Jonathan Druart 45f091cbce
Bug 42133: Restore plugins when editing items in batch
It has been broken then fixed then broken again, several times...

This patch fixes 2 problems:
1. The CSRF fix (I think it's coming from there) didn't adjust the 'if show' test and the JS code from plugins was not injected into the template
2. The selector to call the plugin didn't include this view, only the
   add/edit items

Test plan:
Confirm that you can now use cataloging plugins when editing items in
batch

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:15 +00:00
Pedro Amorim 66c5eb6f29
Bug 34973: Update yarn.lock
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:14 +00:00
Owen Leonard cac6637d83
Bug 34973: (follow-up) Remove old Font Awesome asset references from SCO and SCI
The self-checkout and self-checkin templates no longer need explicit
inclusion of Font Awesome CSS because it's rolled into _common.scss and
thus sco.scss.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:13 +00:00
Owen Leonard bbd56c05d4
Bug 34973: (follow-up) Use lib/fontawesome for font location
It makes more sense to use lib/fontawesome/webfonts as the source
directory for Font Awesome font files rather than bootstrap/css/fonts.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:12 +00:00
Jonathan Druart e169d53c58
Bug 34973: Adjust opac-curbside-pickups.tt
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:11 +00:00
Owen Leonard 1e8c552fa9
Bug 34973: Upgrade Font Awesome in the OPAC to 7.2
We are using Font Awesome 6.3 in the OPAC. The current version is 7.2.
Upgrading will eliminate the repeated warnings in the browser console
about "Glyph bbox was incorrect."

The patch adds Font Awesome to the list of packages in node_modules and
moves the Font Awesome SCSS into _common.scss so that it can be bundled
along with Bootstrap and other 3rd party assets.

Despite the fact that _common.scss grabs FA SCSS files directly from
node_modules I've added the FA font files to css/fonts. I was unable to
see how to reference the webfonts directory in
node_modules/@fortawesome.

The many changed .inc and .tt files are a result of a missed aspect of
our upgrade to Bootstrap 5. The "sr-only" class was removed in BS5 but
the Font Awesome CSS included its own "sr-only" class. This made up for
the fact that we missed the required upgrade to BS5's "visually-hidden."
As soon as Font Awesome's "sr-only" was gone all these mistakes were
made visible.

To test, apply the patch and run 'yarn install.'
- Rebuild the OPAC SCSS.
- Clear your browser cache.
- Test many pages in the OPAC to confirm that no icons are missing.
- Check changes to the templates to confirm that "sr-only" has been
  replaced with "visually-hidden."
  - Places where a broken "sr-only" class would be obvious: Star ratings
    on the user summary page, <legend>s on the search history page.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:10 +00:00
Martin Renvoize 1d5c4cf19d
Bug 41705: (follow-up) Hide receipt page buttons when printing
The Print receipt and Close buttons added in the previous patch use
class="no-print", but printreceiptinvoice.css had no @media print rule
for that class — so they appeared in the printed output (e.g. when
saving as PDF or auto-printing in browsers that block popups).

Add a @media print rule so the buttons remain visible on screen but
disappear from the printed receipt.

Test plan:
1. Disable browser popups (Chrome: "Don't allow sites to send pop-ups
   or use redirects").
2. Enable FinePaymentAutoPopup.
3. Complete a POS sale.
4. On the receipt page, choose Print to PDF (or trigger auto-print).
5. Verify the printed/PDF output contains only the receipt body — no
   Print receipt or Close buttons.
6. Verify the buttons remain visible on screen.

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:09 +00:00
Martin Renvoize 23320ff13c
Bug 41705: Use PRG pattern for POS receipt printing to avoid popup blockers
The previous approach triggered popup blockers because the receipt window
was opened via JavaScript after a page reload — browsers treat this as a
non-user-initiated action. This patch reverses the flow: when
FinePaymentAutoPopup is enabled, the payment form submits directly to a
new tab via target="_blank" (a user-initiated action that browsers allow),
the server processes the payment and redirects (303) to the receipt page,
which auto-prints and closes itself, returning focus to the POS tab.

When FinePaymentAutoPopup is disabled, the server redirects back to the
POS page with payment details in the URL (standard PRG), displaying the
existing success banner with print/email receipt buttons.

Changes:
- pos/pay.pl: Implement PRG redirect for both popup and non-popup flows,
  URI-encode redirect parameters, remove session-based state management
- pos/pay.tt: Add target="_blank" when FinePaymentAutoPopup is enabled,
  reset form state after submission via same-scope setTimeout
- pos/printreceipt.pl: Add error handling for missing receipts, pass
  autoprint parameter to template
- pos/printreceipt.tt: Add conditional auto-print, manual print/close
  buttons, error display, and IntranetSlipPrinterJS preference support
- slip-print.inc: Modernize jQuery to vanilla JS, remove obsolete
  Chrome setTimeout hack

Test plan:
1. Enable FinePaymentAutoPopup system preference
2. Go to Point of Sale, add items, and complete a sale
3. Verify receipt opens in a new tab without popup blocker warnings
4. Verify receipt auto-prints and tab closes after printing
5. Verify original POS tab form is reset for next transaction
6. Disable FinePaymentAutoPopup
7. Complete another sale
8. Verify POS page shows success with Print/Email receipt buttons
9. Test receipt printing from other pages (circulation, fees) to
   confirm slip-print.inc change causes no regressions

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:08 +00:00
Owen Leonard 7bde3cbb79
Bug 42475: (follow-up) Fix a couple more instances of 'opac'
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:07 +00:00
Jonathan Druart 93f4a7f65b
Bug 42475: Fix opac vs OPAC
Correct "opac" with "OPAC" in the pref file

Test plan:
git grep opac **/*.pref
and confirm that there is no relevant occurrences in the output (i.e.
no strings displayed on the UI)

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-03 12:08:03 +00:00
Pedro Amorim 8c4344cced
Bug 42705: Fix SCO spec WebBasedSelfCheck setup
cy.login() in beforeEach broke the test in two ways: the intranet staff session cookie bled into the OPAC request (HTTP cookies have no port scope in
browsers), and navigating from intranet→OPAC mid-test hit a cross-origin block in Cypress. The afterEach suffered the same race condition - the syspref POST fired before the login redirect completed.

Replace with cy.request(), which makes HTTP calls at the runner level without touching the browser's current origin. cy.visitOpac() is then the first
browser navigation, so no cross-origin issue arises. A three-step flow satisfies Koha's CSRF middleware:
1) GET the login page to obtain a session and its token
2) POST credentials to authenticate
3) POST to the svc endpoint.

This fixes the #barcode and #patronlogin not found issues

The CSRF token also requires encodeURIComponent() — base64 uses +, which a
URL-encoded form body decodes as a space, silently corrupting the token and causing the login to fail.

This resolves the 403 Forbidden issue

Everything must pass:
$ cypress run --spec t/cypress/integration/OPAC/SCO_spec.ts

as well as npm run cypress open (cypress ui)

ktd selenium:
$ perl /kohadevbox/misc4dev/run_tests.pl --run-only t/cypress/integration/OPAC/SCO_spec.ts

squash

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-02 15:07:22 +00:00
Pedro Amorim d5ae629d8c
Bug 42705: Add HttpClient_spec.ts
This fails without the fix patch in this set:
cypress run --spec t/cypress/integration/HttpClient_spec.ts

It passes with the fix

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-02 15:07:21 +00:00
Pedro Amorim bdd552a05d
Bug 42705: Make http-client.js handle svc endpoint error
The afterEach calls cy.login() then immediately posts to the syspref endpoint. On CI, the machine is slow enough that the POST fires before the login
redirect completes — no valid session yet — so the endpoint returns a C4::Service auth error in this format:

{ "type": "auth", "message": "expired" }

http-client.js only knew how to parse modern REST API errors (json.error or json.errors[]). Neither field exists here, so json.errors.map(...) crashed with a TypeError instead of surfacing the actual error message.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-02 15:07:19 +00:00
Pedro Amorim dc59aadc4a
Bug 42733: Add tests
Apply tests patch only, run:
yarn cypress run --spec t/cypress/integration/Tools/ManageMarcImport_spec.ts

Confirm it fails with the same error as Jenkins:
Timed out retrying after 10000ms: `cy.wait()` timed out waiting `10000ms` for the 2nd request to the route: `jobPoll`. No request ever occurred.

Apply fix patch. Run again.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-02 15:07:18 +00:00
Pedro Amorim b6af7a6c3a
Bug 42733: Job progress fix
waitForJobFinished previously had no handling for failed jobs or
non-200 responses — if the job API returned anything unexpected,
the recursive cy.wait() would silently stall waiting for a 2nd
poll that never came, producing a confusing timeout. Adds an
explicit "failed" status check and updates the intercept
to reproduce the exact CI failure (Apache returning 503 during
reset_all).

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-02 15:07:17 +00:00
Martin Renvoize 726b205be2
Bug 42726: Add 26.11 release team
Add the 26.11 release team as voted at the development meeting.

Pedro Amorim as Release Manager, with Tomás Cohen Arazi and Martin
Renvoize as RM Assistants. Lisette Scheer as QA Manager with a
19-person QA team. David Cook as Security Release Manager.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-02 15:07:16 +00:00
Martin Renvoize 3b4c09d753
Bug 42726: Fix 25.11 QA team list to match dashboard
Update the 25.11 QA team to reflect the definitive list from the
dashboard. Remove Aleisha Amohia and Julian Maurice who did not
appear in the final count, and add John Vinke, Matt Blenkinsop,
Alex Carver and Victor Grousset who did. Reorder by descending
QA count.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-02 15:07:14 +00:00
Pedro Amorim c98e3a8247
DBRev 26.06.00.000: Start of a new release cycle
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-06-02 15:07:13 +00:00
Lucas Gass e4bb3afa5b
Koha 26.05.00 is here!
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-26 14:16:10 -06:00
Lucas Gass 0a68a90d39
26.05.00: Add release notes
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-26 14:16:10 -06:00
Lucas Gass 394f669003
26.05.00: Update kohastructure.sql
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-26 14:16:09 -06:00
Lucas Gass c24ce043df
26.05.00: Update history.txt
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-26 14:16:09 -06:00
Lucas Gass 49a792d898
26.05:00: Update contributors.yaml
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-26 14:16:02 -06:00
Jonathan Druart 147720ca1d
Bug 42606: Add Cypress tests
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-26 08:37:34 -06:00
Jan Kissig 9066cca3dd
Bug 42606: (QA follow-up): Fix order of checking for frameworkcode
This adds more verbosity to the check for frameworkcode and changes the order of setting a default value '' for it.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-26 08:37:34 -06:00
Nick Clemens bb47a5ffe6
Bug 42606: Check for default framework earlier
The code switches 'Default' to '' - we should just do it immediately after fetching from the template

1 - From the cataloging home page, click "New from Z39.50" without choosing a framework in the dropdown menu.
2 - Perform a Z39.50 search in the popup window, then click "Import" for one of the results. This triggers an error in the parent window:
3 - Error:Can't call method "is_fast_add" on an undefined value at /kohadevbox/koha/cataloguing/addbiblio.pl line 518
4 - Apply patch
5 - Reload and repeat
6 - Success!

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-26 08:37:33 -06:00
Jonathan Druart 7cdae514a4
Bug 27826: Turn on WebBasedSelfCheck to fix Cypress tests
On Jenkins, SCO is not setup like it is in ktd after a reset_all
from insert_data.pl
251 sub configure_selfcheckout {

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-26 08:37:26 -06:00
Lucas Gass 4f58ed8953
Bug 27826: (RM follow-up) Attempt to fix failing test
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-22 17:12:22 -06:00
Martin Renvoize 7a7a3ab2c6
Bug 40383: (follow-up) Fix EdifactModal Cypress test reliability
Three fixes to make all 27 EdifactModal Cypress tests reliably pass:

1. insertData.js: fix currency FK constraint violation (use an existing
   currency rather than creating an undefined one).

2. Koha/Edifact/File.pm: add missing use statements for File::Basename
   and File::Temp so the Cypress task can load the module.

3. EdifactModal_spec.ts:
   - Replace arbitrary cy.wait() calls with condition-based assertions
     throughout (expand/collapse animations, Bootstrap modal state).
   - Fix search timing: add cy.intercept + cy.wait("@ediMsg") in the
     Search Functionality beforeEach so initializeSearch is wired before
     input events fire; use invoke("val")+trigger("input") instead of
     type() to bypass the 500ms debounce race.
   - Fix modal close tests: attach a shown.bs.modal listener before
     opening the modal so we can wait for Bootstrap's 300ms show
     animation to complete (_isTransitioning → false) before calling
     hide(). Without this wait, hide() is silently blocked when the Ajax
     response is faster than the animation (common in KTD). Use
     bootstrap.Modal.getInstance().hide() as the programmatic fallback
     since data-bs-dismiss and keyboard events are unreliable in headless
     Cypress/Electron.
   - Fix edifact_interchange.js: remove duplicate &format=json appended
     to URLs that already contain it.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-22 12:59:11 -06:00
Martin Renvoize 7048a657e7
Bug 40383: (follow-up) Fix failing EdifactModal Cypress tests
Three issues caused the EdifactModal_spec.ts test suite to fail:

1. insertSampleEdifactMessages inserted a vendor with listprice='USD' and
   invoiceprice='USD', but those columns have FK constraints against the
   currency table and USD is not pre-populated in KTD's default install.
   Drop the currency columns from the INSERT so they default to NULL.

2. Koha::Edifact::File::vendor() and basket() called
   Koha::Acquisition::Bookseller and Koha::Acquisition::Basket without
   use-ing them. The edifactmsgs.pl table embeds both on every API call,
   so a missing module caused a 500 and an empty table.

3. showMessage() already appended &format=json before delegating to
   showMessageFromUrl(), which then appended it a second time. Remove the
   redundant copy from showMessage().

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-21 10:49:43 -06:00
Pedro Amorim b5efda989f
Bug 37762: (QA follow-up): Fix insertData.js
cypress run --spec t/cypress/integration/t/insertData.ts

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-21 10:21:40 -06:00
Roman Dolny 171f727734
Bug 41940: Remove uninitialized value warnings in circulation.pl
The warnings:

[WARN] Use of uninitialized value $patron_session_confirmation in split
   at /kohadevbox/koha/circ/circulation.pl line 493.
[WARN] Use of uninitialized value $patron_for_session in numeric eq (==)
   at /kohadevbox/koha/circ/circulation.pl line 494.
[WARN] Use of uninitialized value $session_confirmations in split
   at /kohadevbox/koha/circ/circulation.pl line 495.

appears in the plack-intranet-error.log.

Test plan:
==========
1. Use a fresh KTD, observe plack-intranet-error.log.
2. Have a barcode of existing item.
3. Find any patron and go to "Check out" tab.
4. Check out item.
==> Warnings should appear in plack-intranet-error.log
5. Apply the patch; restart_all.
6. Repeat 2-4 with another barcode.
==> No warnings in plack-intranet-error.log.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: David Nind <david@davidnind.com>

Bug 41940: (follow-up) Syntax corrections

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:58 -06:00
Owen Leonard 6572a5aec0
Bug 42638: Cannot delete an identity provider domain
This patch updates the identity provider domain page so that clicking
the "Delete" button from the list of existing identity providers works
correctly.

There's a lot of unfinished copy and paste from the SMTP servers page,
including a missing chuck of modal dialog HTML.

To test, apply the patch and go to Administration -> Identity providers.

- If necessary, add at least one identity provider.
- Click the "Manage domains" button for an identity provider.
- If necessary, add at least one identity provider domain.
- Click the delete button corresponding to the domain you added.
  - A confirmation modal should appear.
  - Clicking "Close" should close the modal without triggering any
    action.
  - Clicking "Delete" should delete the domain.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:58 -06:00
Jonathan Druart c1a369d274
Bug 42467: Init mmtas to an empty array
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:57 -06:00
Owen Leonard 561e8180ce
Bug 42467: Remove event attributes from MARC modification templates template
This patch removes several "onChange" event attributes from
marc_modification_templates.tt and moves the event handlers into
marc_modification_templates.js.

The patch also corrects a view minor zxlinting errors in the JS file.

To test, apply the patch and clear your browser cache if necessary.

- Go to Cataloging -> MARC modification templates.
- Create a new template if necessary.
- Click "New action."
- Test the behavior of dropdowns and checkboxes in the new action form.
  - Fields should be correctly shown or hidden based on the selected
    actions
  - Fields should be correctly shown or hidden based on the "RegEx"
    checkbox.
  - Text the conditional dropdown along with the "matches" dropdown.
    - Test that the "matches" regex fields are correctly displayed
      according to the checkbox.
- The browser developer console should show no "Content-Security-Policy"
  errors.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:57 -06:00
Owen Leonard 754659d9d0
Bug 42448: Staff Interface News (newsfooter) text does not have sufficient color contrast
This patch updates the CSS which controls the display of the news item
footer ("Posted by" and edit/new links) in order to improve
accessibility.

To test, apply the patch and rebuild the staff interface CSS.

- If necessary, create at least one news item with the display location
  "Staff interface."
- On the staff interface home page, look at the news items in the
  left-hand sidebar. The "Posted by" line should be clear. A check of
  the text's contrast should show that the contrast is appropriate.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:56 -06:00
Owen Leonard 1275a24115
Bug 42445: Remove event attributes from list creation template
This patch removes a couple of "onChange" event attributes from the
lists template in favor of defining the event handler in the footer JS.

To test, apply the patch and go to Lists -> New list.

- Choose "Private" from the public dropdown.
- In the "Allow changes to contents from" dropdown select "Permitted
  staff only"
  - A note should appear about the setting having no effect.
- You should see a similar note displayed when you select "Staff only"
  or "Anyone seeing this list"

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Amended-by: Jonathan Druart
Tidy

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:56 -06:00
Owen Leonard 01a1696058
Bug 42442: Remove event attributes from bibliographic record merge template
This patch moves a couple of "onclick" event attributes from the
bibliographic record merge template, moving the event handling to the JS
block.

This patch also makes a minor change to make sure that the
framework code dropdown preselects the framework of the first of
the records to merge. This brings it in line with the authority
merge template.

To test, apply the patch and perform a catalog search which will return
multiple results including results with different frameworks. In the
standard test data a search for "green" will return two results with two
different frameworks.

- Check the checkbox for at least two records in your search results.
- Click "Edit -> Merge records" in the toolbar at the top of the
  results.
- On the "Merging records" page you should see a frameowrk dropdown. It
  should have preselected the framework corresponding to the first of
  the records you selected.
- When you click the radio buttons next to your records to merge the
  framework selection should change to match your selection..

Sponsored-by: Athens County Public Libraries
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:55 -06:00
David Cook 00655de4d0
Bug 42317: Update minimum version for MARC::File::XML
This patch updates cpanfile to reflect the real minimum version
needed for MARC::File::XML. This is particularly important
due to vulnerabilities in version 1.0.1

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: mason james <mtj@kohaaloha.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:55 -06:00
Lucas Gass 1ed156ef40
Bug 42545: (RM follow-up) Tidy test file
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:54 -06:00
Martin Renvoize 0a0dcee152
Bug 42545: (QA follow-up) Add unit tests for holiday subtraction when return time is before due time
Adds a dedicated subtest covering the specific scenario: daily loans have a
due time of 23:59, so a return earlier in the day on a holiday would
previously cause days_between to exit its loop before processing the end
date, silently skipping the holiday subtraction.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:54 -06:00
Martin Renvoize 6ca7c1118b
Bug 42545: (QA follow-up) Add POD for _init and _holidays and fix incorrect test expectation
The existing 'days: 289 hours, 2 holidays' test asserted 8-1=7 but its own
description named 2 holidays. The expected value matched the buggy behaviour
(loop exiting before the holiday end date) rather than the correct result.
Update to 8-2=6 to reflect the fix.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:53 -06:00
apirak 9c38d74c98
Bug 42545: Koha::Calendar::days_between skips holiday subtraction for end date if time is early
In Koha::Calendar::days_between, the holiday-subtraction loop uses a comparison
that includes time. If the end_dt is a holiday and its time is earlier than
the start_dt time (common in renewals), the loop terminates before processing
the end_dt, leading to an extra fine day.

This patch truncates both dates to the beginning of the day after cloning
and setting the time zone, ensuring the loop correctly processes all
calendar days between the two dates.

Test Plan:
1. Set 'finesCalendar' syspref to 'noFinesWhenClosed'.
2. Set 'CalculateFinesOnReturn' syspref to 'Yes'.
3. Set 'finesMode' syspref to 'Production'.
4. Define a special holiday for today in the library calendar.
5. Identify an overdue item for a patron. Manually set its 'date_due' in the
   database to several days ago with a time of 23:59:00.
6. Renew the item today at any time BEFORE 23:59:00 (e.g., 10:00 AM).
7. Observe that an extra day of fine is charged because today (the holiday)
   was not subtracted.
8. Apply this patch.
9. Repeat the test and observe that the holiday is now correctly subtracted.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-20 09:04:47 -06:00
Jan Kissig cf984da25a
Bug 42412: Fix Incorrect date error in db-revs when using MySQL
In Bug 36466 db_revs/251200019.pl was introduced to update wrong date values (0000-00-00) with NULL.
This can fail on MySQL as it is running with sql_mode NO_ZERO_DATE activated which does not allow to select or update date fields with values='0000-00-00'.

This patch changes the update queries in db_revs/251200019.pl to use Installer::sanitize_zero_date on both columns.

Test plan:
As the test shall be done using MysQL and Mariadb start 2 ktds:
MySQL) DB_IMAGE=mysql:8.0 ktd --proxy --name mysql8 up -d
MariaDB) ktd --proxy --name maria up -d

a) checkout a version prior 36466: git checkout v24.11.14-1
b) reset_all
c) Connect to staff interface (mysql8-intra.localhost and maria-intra.localhost)
d) Go to Serials and create a new subscription
e) run the following to enter database client in each ktd shell: koha-mysql kohadev
f-mysql) run the following commands to be allowed to change planneddate and publisheddate to '0000-00-00':
 SET SESSION sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
 update serial set planneddate = '0000-00-00', publisheddate = '0000-00-00';
f-maria)
 update serial set planneddate = '0000-00-00', publisheddate = '0000-00-00';
g) verify via: select * from serial;
h) checkout main via: git checkout main
i-mysql) run  updatedatabase
 Upgrade to 25.12.00.019  [09:35:55]: Bug 36466 - Fix the incorrect 0000-00-00 date in planneddate and publisheddate field
        ERROR: {UNKNOWN}: DBI Exception: DBD::mysql::db do Incorrect date error in db-revs when using MySQL

        In 36466 db_revs/251200019.pl
        for column 'planneddate' at row 1 at /kohadevbox/koha/C4/Installer.pm line 818
i-maria) do not run updatedatabase yet
j) apply patch
k-mysql) run  updatedatabase
Upgrade to 25.12.00.019  [09:37:02]: Bug 36466 - Fix the incorrect 0000-00-00 date in planneddate and publisheddate field
        Incorrect date planneddate and publisheddate fixed
...
-> is successful now
k-maria) run updatedatabase
l) verify via: select * from serial;

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-19 09:13:44 -06:00
Brendan Lawlor fef2a118e2
Bug 42637: Check if biblio exists before checking if it is fast add
This patch reorders code to make sure the biblio record exists before
checking if the framework is a fast add framework.

To test:
1. Go to a non existing biblio record like:
   http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=99999999
2. See a 500 error or in ktd:
   Can't call method "frameworkcode" on an undefined value
3. Apply patch and restart_all
4. Repeat step 1
5. Now you get the nice error message:
   The record you requested does not exist (99999999).

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 16:05:41 -06:00
Lucas Gass ee01f1746c
Bug 38414: (follow-up) Fix OR operator
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 16:05:35 -06:00
Nick Clemens 9942ca9928
Bug 42603: Correct invoice_id variable
To test:
1 - Open a basket, place some orders, close the basket
2 - Receive shipments
3 - Select an order and click 'Receive selected'
4 - Nothing happens, error in console
5 - Apply patch, reload page
6 - Can successfully order

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:22 -06:00
Lucas Gass 1a0501f5e8
Bug 42262: encodeURIComponent for the 006 value
To test:
1- have a MARC record with Type of Material "BKS - Books" that does not have an 006 defined (biblio 1 in the KTD default data will work)
2- edit the biblio in the basic editor
3- open the tag editor for the 006
4- observe that the values along the top of the tag editor window show a value of "" (blank) in position 17
5- click Save in the tag editor window
6- confirm that the values in the 006 tag end with a space
7- open the 006 tag editor again
8- observe that the values along the top of the tag editor window no longer show any value for position 17. Instead, they end with the zero in position 16
9- click Save in the tag editor window
10- confirm that the values in the 006 tag now end with a zero, not followed by a space
11- save your biblio
12- edit the biblio in the basic editor again, get warning "The following fixed-length control field(s) have an invalid length: 006. Please fix."
13- APPLY PATCH and restart_all
14. Try editing the 006 value again, the whitespace should be preserved and you get no error.

Signed-off-by: Justin Swink <Justin.Swink@littleelm.gov>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:21 -06:00
Laura_Escamilla 88676495e0
Bug 42236: Add accessible label to OPAC user lists actions column
To recreate:
1. Log in to the OPAC
2. Create at least 1 list
3. Go to the "Your lists" page (opac-userlists)
4. Inspect the table header for the Edit/Delete column
5. Confirm the header contains no text

Test the patch:
1. Apply the patch
2. Repeat steps 1-3
3. Inspect the table header for the Edit/Delete column
4. Confirm the header now contains accessible text
5. Confirm Edit and Delete actions still function as expected
6. Run an accessibility checker and confirm the issue is resolved
7. Sign off and have a wonderful day! :D

Signed-off-by: John Vinke <jvinke@austinseminary.edu>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:21 -06:00
Kyle M Hall 7dc223b30a
Bug 42547: Fix SIP performance if sip2_resource_last_modified not set in cache
In Koha, the sip2_resource_last_modified key is updated any time the SIP config is edited from the SIP2 editor in the staff interface ( institutions, accounts, system preference overrides ).

If sip2_resource_last_modified is missing from memcached, Koha SIP emits "Couldn't find sip2_resource_last_modified, considering configuration not up to date" in the logs, and fetches the entire configuration from the database, *for each request*.

This causes SIP performance to drop significantly. In my production testing with 83 institutions and 171 accounts the login message response time dropped from 0.283 seconds to 2.899 seconds.

I think the solution is pretty simple. If there is a cache miss on sip2_resource_last_modified, SIP should load the config from database as it does already, but then it should also set sip2_resource_last_modified!

Test Plan:
1) Apply this patch
2) Prove t/db_dependent/Koha/SIP2/SIP2ModuleMigration.t

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-18 09:30:20 -06:00
Kevin Carnes c7ee854d9f
Bug 42176: Can cancel add authorized value
The cancel add authorized value button has no type, so it will submit the form.
This patch makes it a button so it does not submit the form.

Test plan:
1.  Ensure that the CreateAVFromCataloguing preference is enabled.
2.  Edit an item.
3.  Enter an authorized value that does not exist (e.g. shelving location).
4.  Select it to be able to create it.
5.  Click the Cancel button to not create a new authorized value.
6.  Observe that the form was sent or that a new authorized value was created.
7.  Delete the new authorized value.
8.  Apply the patch.
9.  Repeat steps 2-5.
10. Observe that the form was not sent or that a new authorized value was not
    created.
11. Sign off.

Sponsored-by: Lund University Library
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:20 -06:00
Jonathan Druart e109e09ae4
Bug 42581: Exclude plugins' routes from xt/api.t
If some plugins are installed and inject REST API routes, xt/api.t may fail.

Test plan:
You can install the Jump plugin (https://gitlab.com/joubu/koha-plugin-jump) and run xt/api.t
It will fail:
```
get /contrib/jump/parse -> uses tag 'parse' not present in top level list
get /contrib/jump/search -> uses tag 'search' not present in top level list
get /contrib/jump/search -> uses tag 'objects' not present in top level list
get /contrib/jump/static/static_files/commandPalette.js -> uses tag 'Jump' not present in top level list
get /contrib/jump/static/static_files/jump.css -> uses tag 'Jump' not present in top level list
[...]
```

Apply this patch and run `prove xt/api.t` again
=> It should pass

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:19 -06:00
Jonathan Druart 4da822b6a1
Bug 42578: Prevent Koha/Hold.t to fail on date comparison
We must use t::lib::Dates::compare to compare dates in test.

Test plan:
Confirm that `prove t/db_dependent/Koha/Hold.t` still returns green
after this patch

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:19 -06:00
Jonathan Druart 26abf4c73d
Bug 42521: (bug 39055 follow-up) Fix login from suggestion.pl
If you try to login from suggestion/suggestion.pl you get a 500

DBIx::Class::Storage::DBI::_dbh_execute(): DBI Exception:
DBD::mysql::st execute failed: Unknown column 'login_op' in 'WHERE' at
/kohadevbox/koha/Koha/Objects.pm line 401

Test plan:
Logout
hit /cgi-bin/koha/suggestion/suggestion.pl
login
=> Without this patch you get a 500
=> With this patch applied you get the suggestion list view

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:18 -06:00
Eric Phetteplace f7071e91fe
Bug 42244: Fix JS TypeError on patrons ILL table
The multipleSelect plugin is used on the ILL requests table but is not
present when viewing a patron's ILL request history. This patch avoids a
JS TypeError by checking if the plugin exists before calling it.

To test:
- Enable ILLModule setting
- ILL Requests > New ILL Request
- Metadata doesn't matter but attach to a patron account, e.g. use the
"Acevedo" surname on KTD
- Go to the Patron > ILL requests history
- View browser's JS console, note TypeError
- Apply patch
- Refresh page, TypeError goes away

Signed-off-by: Chris Mathevet <chris.mathevet@inLibro.com>

Amended-by: Jonathan Druart
Tidy

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:18 -06:00
elias 850f1f224f
Bug 41373: Fix Mana report sharing when only one language is enabled
Test plan :
- Disable all but one language in system preference
  StaffInterfaceLanguages
- Start a local instance of Mana
  - Clone the mana repo on your machine (from
    https://gitlab.com/koha-community/koha-mana)
  - Edit compose/base.yml and uncomment the line:
    ${PWD}/sql/mana-sample.sql:/docker-entrypoint-initdb.d/test-data.sql
  - Run `bin/mana --skip-email-validation up -- -d` to launch mana and
    create a docker network koha will connect to
- Run `ktd --mana up -d` to connect koha to mana
- In Koha staff interface, go to "Administration » Share content with
  Mana KB":
  - Set "Share content with Mana KB" to "Yes", click "Save"
  - Fill the form under "Configure Mana KB", click "Send to Mana KB"
- Try to share a report to mana. Go to Mana UI to verify that the report
  was correctly shared

Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Emmanuel Bétemps <e.betemps@gmail.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:17 -06:00
Jan Kissig 4a71a8476e
Bug 42466: Fix ignored form validation on paycollect
When UseCashRegisters is enabled and a change value bigger than 0 exists (the amount tendered is bigger than the amount paid) the form could be submitted without a required cash register.
The modal dialog informing of the change value would simply ignore missing other required fields.

Also warnings on 'Amount tendered' did not disappear when 'Amount being paid' was set to a higher value and later reset to a lower value than  'Amount tendered'.

This patch fixes both issues by moving the modal dialog (confirm change money) to the form validations submit handler and by validating the form on each update of 'Amount tendered' or 'Amount being paid'.

Test plan:
a) enable UseCashRegisters and create one under /cgi-bin/koha/admin/cash_registers.pl?op=add_form
b) create a manual fee (f.e. 10) f.e. via /cgi-bin/koha/members/maninvoice.pl?borrowernumber=16
c) There are several ways to reach the 'Pay an amount toward all charges' form:
  - Patron -> Accounting -> (Tab) Make a payment
  - Patron -> Details -> Attention -> Pay all charges
d) enter the follwing values
  - Amount being paid: 7
  - Amount tendered: 6 -> a warning will appear : Please enter a value greater than or equal to 7.
e) set
  - Amount being paid: 5 -> The warning remains visible: Please enter a value greater than or equal to 7.
f) Click confirm -> Modal which wants you to confirm that there is a change left.
g) Click yes. The form will be sent even though the required filed 'Payment type' is not set.
h) go to the payments form again. Total amount outstanding should be 5.00 now
enter the following so that change is > 0.00
 - Amount being paid: 3
 - Amount tendered: 4
 - Payment type: Cash
i) Click confirm -> Modal which wants you to confirm that there is a change left.
j) Click yes. You will see an Error trace page

As long as 'Change to give' is other than 0.00 you will be able to send the form.

Apply patch:
l) repeat b) until e) and check that the warning disappears when Amount being paid is lower or equal than Amount tendered
m) check that clicking Confirm will not send the form
n) check that when Payment type is set to Cash, the Cash register is required to send the form.

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

Amended-by: Jonathan Druart
Tidy
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:17 -06:00
Jan Kissig f4678e5b23
Bug 42398: Reimplement form validation to news
This patch reimplements form validation on news (additional contents). The form.validate() call which adds a submitHandler is moved outside the document ready block in order to work again.
Markup like class=required is added to meet JS10 guidelines. JS alerts (when default title or default content was empty) were replaced by setting focus the required field.

Test plan:

a) go to /cgi-bin/koha/tools/additional-contents.pl?category=news (Tools -> News)
b) click either 'New entry' or 'New entry using text editor'
c) enter a required publication date
d) save the form. You will not be required to enter a default title or content.
e) apply patch followed by a restart_all
f) go to /cgi-bin/koha/tools/additional-contents.pl?category=news (Tools -> News)
g) click either 'New entry' or 'New entry using text editor'
h) check that title and content in the 'Default' tab are marked as required
i) try to send form f.e. when only title and content in 'English' tab are set
j) check if default title or content is missing the default tab is shown and the field gets focused

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:16 -06:00
Andrew Fuerste Henry 186ec335db
Bug 42570: Use count_holds for OPAC patron summary pending holds
To test:
1 - enable DisplayAddHoldGroups
2 - place two ungrouped holds for a patron
3 - log into the opac as your patron, see 2 pending holds
3 - group the two holds
4 - reload OPAC, still see 2 pending holds
5 - Apply patch, restart_all
6 - reload OPAc, see 1 pending holds
7 - ungroup holds
8 - reload OPAC, see 2 pending holds

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:16 -06:00
Pedro Amorim e931157dc7
Bug 42255: Add tests
prove t/db_dependent/Reserves.t

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:15 -06:00
Pedro Amorim e56c9cc9bb
Bug 42255: Maximum total holds allowed (count)
Make 'Maximum total holds allowed (count)' (aka max_holds) consider hold groups

Test plan:
Same as the previous test plan, but set 'Maximum total holds allowed (count)' as 2 and instead set 'Holds allowed (total)' as unlimited.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:15 -06:00
Pedro Amorim d5f130da5a
Bug 42255: Holds allowed (total)
Make 'Holds allowed (total)' (aka reservesallowed) consider each hold group as an individual hold

Test plan:
1) Enable DisplayAddHoldGroups sys pref
2) Set Holds allowed (total) to 2, visit:
http://localhost:8081/cgi-bin/koha/admin/smart-rules.pl
3) Scroll to the right, click the 'Edit' button under 'Actions' column.
4) On the 'Holds allowed (total)' column cell, enter '2' and click 'Save' under the 'Actions' column to the right.
5) Search  for 'test':
http://localhost:8081/cgi-bin/koha/catalogue/search.pl?q=test
6) Tick the checkbox next the first and second results, click 'Place hold' button at the top toolbar
7) Search for 'koha' user and pick Koha (42)
8) Set a pickup location for each hold and tick the 'Treat as hold group' checkbox. Click 'Place holds'.
9) Try to place another single hold for the same patron. Should succeed, because the 2 grouped holds count as 1. Patron now should have 1 group + 1 ungrouped hold = effective count of 2.
10) Try to place yet another hold, should be blocked with a tooManyHolds error.
11) Prior to this patch, step 9 would've failed because the 2 grouped holds counted as 2.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-18 09:30:08 -06:00
Lucas Gass 11d7450e2a
Bug 37762: (RM follow-up): Tidy t/db_dependent/api/v1/iso18626/request.t
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:13 -06:00
Pedro Amorim 6847e6d002
Bug 37762: (QA follow-up): Display translatable string directly on template
No script.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:13 -06:00
Pedro Amorim 06c4b3c138
Bug 37762: (QA follow-up) Utilize format_datetime
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:12 -06:00
Pedro Amorim 4b525d9e3e
Bug 37762: (QA follow-up): Warn and return 500 on schema validation failure in progress_request
An exception is not implemented as progress_request is called in
different circulation contexts (hold is placed, checkout happens,
etc) and those actions should not be dependent on the outcome of
progress_request, i.e. if progress_request fails due to schema
validation failure, the circulation action should happen as before.

prove t/db_dependent/Koha/ILL/ISO18626/Request.t
prove t/db_dependent/api/v1/iso18626/request.t
cypress run --spec t/cypress/integration/ILL/SupplyingRequests.ts

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:12 -06:00
Pedro Amorim 56d4be4cc2
Bug 37762: (QA follow-up): Add translatability to missing strings
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:11 -06:00
Pedro Amorim 843513d8ff
Bug 37762: (QA follow-up): Remove v-html directive from ShowElement.vue
This reimplements how patronAutoComplete behaves on ShowElement, keeping the same functionality but dropping v-html

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:11 -06:00
Pedro Amorim 3e7a607f03
Bug 37762: (QA follow-up): Revert LinkWrapper template v-else v-html
The v-html directive was unintentionally left on the fallback <template>
in LinkWrapper during the introduction of the ILL module. It has no
effect and should not have been committed.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:10 -06:00
Pedro Amorim 4cf61f6c73
Bug 37762: (QA follow-up): Fix cypress ILL tests
cypress run --spec t/cypress/integration/ILL/*

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:10 -06:00
Pedro Amorim 3c80cbb94b
Bug 37762: (QA follow-up): Remove obsolete code
This is leftover from original implementation of progressRequest when it was it's own component.
All functionality is now in SupplyingResourve.vue instead

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:09 -06:00
Pedro Amorim 9164ddc4c7
Bug 37762: (QA follow-up): Remove obsolete code
This is leftover from original implementation.
It since moved to js/vue/stores/ill.js and is managed by ILL/Main.vue

To test:
1) Access (with ILLModule enabled and disabled):
http://localhost:8081/cgi-bin/koha/ill/ill.pl
2) Confirm you get the module if ILLModule is enabled, or a message if
   it's disabled

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:09 -06:00
Pedro Amorim 9a57ce1c0f
Bug 37762: (QA follow-up): Remove obsolete code
This was leftover from a previous approach at implementing it, before
hooking into holds/circulation was entirely holds authoritative.

As it currently stands, the note and expectedDeliveryData form inputs are
rendered and handled by the request.tt form when placing a hold, if a
supplyill param exists.
The params are then sent to AddReserve by placerequest.pl which in turn
sends it to progress_iso18626_request.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:08 -06:00
David Cook 1224f950e8
Bug 37762: (QA follow-up) remove reintroduced visual_hold_group_id
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-15 08:11:01 -06:00
wainuiwitikapark 7b1a348ac9
Bug 38009: Add a generate next button in serials receive page
Adds a generate next button to the serials receive page (serials-edit.pl) that works similar to the button on the serials collection page.

To test:

1. Go to Staff Client > Serials
2. Find an existing subscription or create a new one
3. Go to the Serials collection page for your serial e.g. /cgi-bin/koha/serials/serials-collection.pl?subscriptionid=1
4. Click on the "Generate next" button and notice that the table adds
   another row
5. Click on the "Receive" button at the top of the page
6. Notice that the page also shows the issues like the rows in the
   previous page
7. Apply patch
8. Repeat steps 1-5
9. Notice that the page also has a "Generate next" button at the top
10. Check that the button generates a new issue row on the page

Sponsored-by: Pymble Ladies' College
Signed-off-by: esther <esther@bywatersolutions.com>
Signed-off-by: Brendan Gallagher <brendan@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:46 -06:00
Jan Kissig 5e7cb0cf58
Bug 42287: (QA follow-up) Split JS to seperate TT and tidy
This splits the script tag in two_factor_auth.tt and member-flags.tt to have a seperate part for interploated TT variables.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:46 -06:00
Jonathan Druart e16a9e92b5
Bug 42287: Tidy all script tags - members (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:45 -06:00
Jonathan Druart 4020a70819
Bug 42287: Tidy all script tags - members
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the members/patrons module.

The 2 main changes are in the following files:
* moremember.tt
=> Trivial, simply moving the existing JS variable to a separate script tag
* update-child.tt
=> Trivial, only a single variable created: 'patron_id'

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:45 -06:00
Lucas Gass af6eca85fb
Bug 42150: (RM follow-up) Make QA script happy
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:44 -06:00
Paul Derscheid 929cb64763
Bug 42150: (follow-up) Remove duplicate h export
- The explicit `export { h }` from the "Re-export h" follow-up
  conflicts with `export * from "vue"` added in the "Share Vue
  via import map" follow-up, since the wildcard re-export already
  includes h
- Remove the redundant named export to fix the rspack build error:
  "Duplicate export of 'h'"

Signed-off-by: Arthur Suzuki <arthur.suzuki@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:43 -06:00
pders01 afc12b1754
Bug 42150: (follow-up) Add nonce to import map script tag
The QA test tools require all <script> elements to have a
src or nonce attribute. Import maps are declarative JSON and
not executed as code, but adding the nonce satisfies the
check and is harmless.

To test:
1. Run qa-test-tools on the patch set and verify all files
   pass the forbidden_patterns check.
2. Load any staff page and verify the import map still works
   (Vue islands render, plugin islands have reactivity).

Signed-off-by: Arthur Suzuki <arthur.suzuki@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:43 -06:00
pders01 9bd6745a53
Bug 42150: (follow-up) Guard against null importFn return
If a plugin's importFn returns null or undefined (e.g. the
static file was removed or the import fails silently), the
frozen module clone would crash with "Cannot convert undefined
or null to object". This adds an early return before the
clone attempt.

To test:
1. Register an island with an importFn that returns undefined.
2. Verify no error is thrown and the island is silently skipped.
3. Other islands on the same page still render normally.

Signed-off-by: Arthur Suzuki <arthur.suzuki@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:42 -06:00
pders01 62434ec4c6
Bug 42150: (follow-up) Share Vue via import map and handle frozen modules
Plugin-provided Vue SFCs need access to the same Vue runtime
that Koha uses for defineCustomElement. Without this, plugins
bundle their own Vue, creating two reactive systems that do
not interoperate (reactivity breaks silently).

- Re-export all of Vue from islands.esm.js so it serves as
  the single source of Vue APIs
- Add import map in main-container.inc mapping "vue" to
  islands.esm.js — plugin builds that externalize Vue now
  resolve to Koha's shared instance
- Clone frozen ES module exports before passing to
  defineCustomElement (module namespace objects are frozen
  per spec, but defineCustomElement needs to set properties)

To test:
1. Verify existing core islands still render (admin-menu,
   acquisitions-menu, vendor-menu).
2. Install the test plugin (test-islanddemo-0.1.0.kpz).
3. Navigate to any staff page and verify the Vue Island
   Demo component renders with styling.
4. Click the counter button repeatedly and verify the count
   updates reactively.
5. Open browser console — no errors except CSP report-only
   warnings.

Signed-off-by: Arthur Suzuki <arthur.suzuki@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:42 -06:00
pders01 434f66aa92
Bug 42150: (follow-up) Re-export h from Vue for plugin use
Plugins that register inline islands need Vue's h() function
to build render trees without requiring a separate Vue import
or the template compiler. Re-exporting h from the islands
module lets plugins import everything they need from a single
source.

To test:
1. From a plugin's intranet_js module script, import h:
     const { registerIsland, h } = await import(islandsSrc);
2. Use h() in a component's setup/render function.
3. Verify the component renders without the "runtime
   compilation is not supported" warning.

Signed-off-by: Arthur Suzuki <arthur.suzuki@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:41 -06:00
pders01 c6e3dcd044
Bug 42150: (follow-up) Validate custom element names
Reject names that do not conform to the web component spec
(lowercase, must contain a hyphen, must start with a letter).
This catches typos early with a clear console warning rather
than letting them fail silently or throw at customElements.define().

To test:
1. Call registerIsland("InvalidName", { ... }) and verify
   a warning is logged and registration is rejected.
2. Call registerIsland("no-problem", { ... }) and verify
   it succeeds.
3. Call registerIsland("123-bad", { ... }) and verify it
   is rejected (starts with digit).

Signed-off-by: Arthur Suzuki <arthur.suzuki@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:41 -06:00
pders01 46253c092f
Bug 42150: Export registerIsland() for plugin use
Add a public API to the islands module so that plugins can
register Vue components as custom elements. Plugin JS from
the intranet_js hook runs before hydrate() fires, so
registrations are picked up during the DOM scan.

The function includes a duplicate guard, attempting to
register a name that already exists logs a console warning
and is a no-op, so plugins cannot overwrite core islands.

The WebComponentDynamicImport type is also exported so that
TypeScript consumers get type safety.

To test:
1. Verify existing islands work unchanged:
   a. Navigate to Administration and confirm admin-menu
      renders.
   b. Navigate to Acquisitions and confirm acquisitions-menu
      and vendor-menu render.
2. Create a test plugin with an intranet_js hook that
   outputs a module script calling registerIsland() with a
   simple inline Vue component served via static_routes.
3. Place the matching custom element tag in a template.
4. Load the page and verify the component renders.
5. Open the browser console and verify no errors.
6. Call registerIsland() with an already-registered name
   and verify the console warning appears and the original
   component is unchanged.
7. Remove the custom element tag from the template and
   verify the component is not loaded unnecessarily.
8. Sign-off

Signed-off-by: Arthur Suzuki <arthur.suzuki@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:40 -06:00
Lucas Gass 730c35f42b
Bug 32773: (RM follow-up) Fix QA failures
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:40 -06:00
Lucas Gass 64eb2c09c9
Bug 32773: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:39 -06:00
Lucas Gass ed9ac6e94b
Bug 32773: DBRev 25.12.00.064
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:38 -06:00
Brendan Lawlor a7912704ea
Bug 32773: (follow-up) Set is_fast_add for sample FA frameworks
Signed-off-by: Miranda Nero <mnero@oslri.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:38 -06:00
Brendan Lawlor 260cbf08cc
Bug 32773: (follow-up) Add Fast add badge to bib detail
Signed-off-by: Miranda Nero <mnero@oslri.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:37 -06:00
Brendan Lawlor 9f8cb217c0
Bug 32773: (follow-up) Unit tests
Signed-off-by: Miranda Nero <mnero@oslri.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:37 -06:00
Brendan Lawlor 5233d589d5
Bug 32773: Add fast cataloging support for multiple frameworks
This set of patches adds a new is_fast_add column to biblio_framework.
The Biblio.pm subroutine can_be_edited now checks if the framework is_fast_add.

In Admin > MARC bibliographic frameworks admin users can add or edit
frameworks to be used as Fast add frameworks.
Users with only fast_cataloging permission will be able to add records
using those frameworks in the Fast Cataloging module.

Test plan:
Apply patches, restart_all and updatedatabase
1. Go to Admin > MARC Bibliographic frameworks
2. Edit/create a new framework and check the 'Use as Fast add Framework' box
3. Also edit a framework and uncheck the 'Use as Fast add Framework' box
4. Notice a 'Fast add' badge is displayed in the table
5. Create a staff user with only fast_cataloging permissions
6. Log in as that user and go to Cataloging > Fast Cataloging
7. Confirm you can add a Fast add record and items
8. Use the 'Settings' drop down to Change Fast add framework
9. Confirm you see the frameworks that you set as Fast add earlier
10. Confirm you can add the record and items
11. Log in as a superlibrarian and confirm that you can add records and items
 using standard frameworks and frameworks set as fast add
12. Set some biblio frameworks as Fast Add frameworks
13. Login as a user with fast_cataloging permissions only
14. Create and edit different types of Fast Add records
15. Add items and edit items
16. Confirm that you can only edit bibs and items
   for bibs where the framework is set as a Fast add framework
   and the item is from your home library.
17. Log in as a superlibrarian
18. Confirm you can still create and edit records with the Default
   and other frameworks
19. Change a record's framework from a Fast add to Default
20. Login as the user with only fast_cataloging confirm that you can
   no longer edit that record or its items

Sponsored-by: CLAMS
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Miranda Nero <mnero@oslri.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:36 -06:00
Brendan Lawlor 4007bb3490
Bug 32773: Update change framework and new record dropdowns
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Miranda Nero <mnero@oslri.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:36 -06:00
Brendan Lawlor 04a48349be
Bug 32773: Add ability to set biblio_framework.is_fast_add
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Miranda Nero <mnero@oslri.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:35 -06:00
Brendan Lawlor c11a324468
Bug 32773: Add column biblio_framework.is_fast_add
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Miranda Nero <mnero@oslri.net>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 16:05:28 -06:00
Jonathan Druart bbf1cf504e
Bug 42177: bundlesEnabled fix
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:31:58 -06:00
Lucas Gass 173f65f70b
Bug 37762: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:56 -06:00
Lucas Gass 4189602829
Bug 37762: (RM follow-up) Fix table order
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:55 -06:00
Pedro Amorim 233cf7e756
Bug 37762: (QA follow-up): Update kohastructure.sql to match atomicupdate
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:55 -06:00
Pedro Amorim b3d078c523
Bug 37762: (QA follow-up): Add duplicate_id exception
To requesting agencies 'update' endpoint

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:54 -06:00
Lucas Gass 580137e64c
Bug 37762: (RM follow-up) Update auth check for new Koha::Controller::Catalogue
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:54 -06:00
Lucas Gass 3075f176e0
Bug 37762: (RM follow-up) Fix swagger defs
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:53 -06:00
Lucas Gass 8a380eff1a
Bug 37762: DBRev 25.12.00.063
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:52 -06:00
Pedro Amorim 3595d2abe8
Bug 37762: (QA follow-up): Address test files QA complaints
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:52 -06:00
Pedro Amorim 353ffe9b7a
Bug 37762: (QA follow-up): Relabel pending requesting agency action
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:51 -06:00
Pedro Amorim bbc4e8050d
Bug 37762: (QA follow-up): Add filters to supply ILL table
Also fix table_settings

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:51 -06:00
Pedro Amorim 926a03b77c
Bug 37762: (QA follow-up): Fix supplying agency request ID label
Also clean-up redundant code pending ra action

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:50 -06:00
Pedro Amorim 8b09ae352e
Bug 37762: (QA follow-up): Add placeholder to callback_endpoint
In requesting agency configuration page

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:50 -06:00
Pedro Amorim 3c75ae097c
Bug 37762: (QA follow-up): Add ShippedReturn support action type
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:49 -06:00
Pedro Amorim 19f7470b40
Bug 37762: (QA follow-up): Add real requesting agency request ID to ISO message
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:49 -06:00
Pedro Amorim 23c85e598e
Bug 37762: (QA follow-up): Add loading while action is processing
This also prevents clicking other action buttons while an action is midflight

cypress run --spec t/cypress/integration/ILL/SupplyingRequests.ts

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:48 -06:00
Pedro Amorim 1064b56e94
Bug 37762: (QA follow-up): Add message with timestamp +1
Instead of waiting a real 1 second

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:48 -06:00
Pedro Amorim 86c479edee
Bug 37762: Add additional tests
cypress run --spec t/cypress/integration/ILL/SupplyingRequests.ts
cypress run --spec t/cypress/integration/ILL/RequestingAgencies.ts
prove t/db_dependent/Koha/ILL/ISO18626/Request.t

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:47 -06:00
Pedro Amorim 79496466cb
Bug 37762: (QA follow-up): Fix checkout for bib-level holds
This will now find the corresponding ISO18626 request and update it accordingly when a check-out happens.

Use-case this is addressing:
1. Place an item-level hold for Henry
2. Check-in that item e.g. 39999000003130 (make it waiting)
3. Create a new ISO18626 request and 'Search to hold', place a bib-level hold on the same bib as above (for patron Walker)
4. Check-out the item from 2. 39999000003130 to Walker
5. Confirm an ISO18626 message input is shown and that the respective ISO18626 request is updated to 'Loaned' once the check-out happens (i.e. override Henry's hold).

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:47 -06:00
Pedro Amorim 98bd736dac
Bug 37762: (QA follow-up): Fix rendering regular holds in table
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:46 -06:00
Pedro Amorim f036c92ec0
Bug 37762: Send message back to requesting agency
This commit applies changes to V1.pm to be able to utilize the same XML methods.
We're also updating tests to reflect this new functionality.

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:46 -06:00
Pedro Amorim ae3b70b66b
Bug 37762: SupplyingResource updates
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:45 -06:00
Pedro Amorim 468a4b73e8
Bug 37762: VueJS framework: Add biblio and checkout API clients
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:45 -06:00
Pedro Amorim c32d08e560
Bug 37762: Catalogue: Hook to biblios
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:44 -06:00
Pedro Amorim db7854682e
Bug 37762: Circulation: Hook to checkouts/checkins
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:43 -06:00
Pedro Amorim 950c9ccb96
Bug 37762: Circulation: Hook to holds
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:43 -06:00
Pedro Amorim 1d6fb7f57d
Bug 37762: Hook to holds preparation: DRY holdfor and searchToOrder
In preparation to implement 'hold for supply ILL'
This DRYs the existing functionality, meaning the new similar functionality will only be added once in the Controller::Catalogue

IMPORTANT:
The following command must only return occurrences in
Koha::Controller::Catalogue before this is pushed to main:
git grep "\->cookie(\"holdfor\")"

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:42 -06:00
Pedro Amorim 87961beb4d
Bug 37762: Bugfix: Display relationshipSelect
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:42 -06:00
Pedro Amorim 8e87fae1cc
Bug 37762: ISO18626 KOHA classes
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:41 -06:00
Pedro Amorim 2f17713637
Bug 37762: ISO18626 Koha DB changes
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:41 -06:00
Pedro Amorim 29c1a17da5
Bug 37762: Add ISO18626 tests
prove t/db_dependent/api/v1/iso18626/request.t
prove t/db_dependent/api/v1/iso18626/requesting_agencies.t

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:40 -06:00
Pedro Amorim f9b9bc77b2
Bug 37762: ISO18626 REST API specs
Reference:
https: //illtransactions.org/schemas/ISO-18626-2021-2.xsd
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:40 -06:00
Pedro Amorim 55fa4609fa
Bug 37762: REST API: Convert XML payload to JSON for swagger definition validation
Convert JSON response to XML if header content_type: application/xml

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:39 -06:00
Pedro Amorim 2a1fa8d091
Bug 37762: Vuejs sysprefs store: Allow fetching multiple sys pref values
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:39 -06:00
Pedro Amorim 6dab32ee02
Bug 37762: VueJS ILL Module
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:38 -06:00
Pedro Amorim b307692fb1
Bug 37762: PatronAutoComplete: VueJS framework: Add patronAutoComplete attribute type
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:38 -06:00
Pedro Amorim df37e571d0
Bug 37762: PatronAutoComplete prep: Abstract rendering of selected patron
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:37 -06:00
Pedro Amorim 48783f8477
Bug 37762: PatronAutoComplete prep: Add on remove callback to patron_autocomplete
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:36 -06:00
Pedro Amorim 7374325216
Bug 37762: PatronAutoComplete prep: Allow additional filters in patron_autocomplete
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:36 -06:00
Pedro Amorim 30161380a9
Bug 37762: Fix confirmation button CSS class in confirmation modal
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:35 -06:00
Pedro Amorim bc0ae34098
Bug 37762: VueJS framework: Add attribute.format check in empty groups check
This keeps the same functionality as before, i.e. hide the whole group
if all attributes within are empty. However it now also checks for
'format' as some resources may want to format the display of empty attributes.

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:35 -06:00
Pedro Amorim 66d7845764
Bug 37762: VueJS framework: Add possibility of external navigation item
e.g.:

{
    path: "/cgi-bin/koha/ill/ill-requests.pl",
    title: $__("Requests"),
    icon: "fa fa-download",
    is_external: true,
    is_navigation_item: true,
},

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:34 -06:00
Pedro Amorim b4fe2b92dd
Bug 37762: VueJS framework: Add 'name' to moduleDashboard
This is required to differentiate between selected widgets on different module dashboards i.e. ERM vs ILL

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:34 -06:00
Pedro Amorim a2c13377ed
Bug 37762: VueJS framework: Toolbarbutton spacing to consider different css classes
There's no spacing currently, but it's not obvious as the current style
has the same background color as the body.
This adds spacing so even if some buttons have different colors (e.g.
primary or danger) all buttons are spaced correctly.

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:33 -06:00
Pedro Amorim a40fa90d6a
Bug 37762: VueJS framework: updateConfirmationDialogInputs
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:33 -06:00
Pedro Amorim 91b4780074
Bug 37762: VueJS framework: Add update event to v-select FormElement
The existing 'selected' event only fires when something is selected, it does not fire when a selection is unselected. onUpdated fires for both.

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:32 -06:00
Pedro Amorim 903da26fe4
Bug 37762: VueJS framework: Fix tooltip within modal dialog
Without this, the tooltip is behind the modal thus invisible

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:32 -06:00
Pedro Amorim bd9dc920de
Bug 37762: VueJS framework: Allow styling v-select dropdown
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:31 -06:00
Pedro Amorim 3e756390cb
Bug 37762: VueJS framework: Update width of inputs inside modal dialog
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:31 -06:00
Pedro Amorim 38b38bdce6
Bug 37762: VueJS framework: Add cssClass consideration to link buttons
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:30 -06:00
Pedro Amorim 675dd1cac0
Bug 37762: VueJS framework: Add patch to http-client.js
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:29 -06:00
Matt Blenkinsop 9e420632e7
Bug 37762: Allow FormElement to accept hidden inputs
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-08 12:25:23 -06:00
Martin Renvoize 6f62a8fe30
Bug 41919: Limit number of current reports a single user can run simultaneously
Building on bug 41918, it would be good to limit the number of reports
a given user can run at a time. This control is config-file based as
it is tied to the server resources provided by the administrator.

Adds a new koha-conf.xml option total_running_reports_per_user_limit
(0 = disabled). When the configured limit is reached, the user is told
"You have too many reports running at the moment. Please wait for some
of them to finish before running this report again."

Note on implementation: the original patch on bz (attachment 198034)
performed a second pre-flight Koha::Reports->running call in
reports/guided_reports.pl. The bug 41918 QA follow-ups already pushed
the duplicate-running guard into Koha::Report::prep_report so it would
fire across the staff UI, svc/report and opac/svc/report. This patch
extends that same model-layer architecture rather than reintroducing
controller-layer enforcement:

- New Koha::Exceptions::Report::TotalRunning subclass of
  Koha::Exceptions::Report.
- prep_report now performs a second check after the duplicate one and
  throws TotalRunning when the user has total_running_reports_per_user_limit
  reports of any kind already in flight.
- All three callers (guided_reports.pl, svc/report, opac/svc/report)
  catch the new exception alongside DuplicateRunning, mapping it to a
  new template error key total_running_reports_limit_exceeded for the
  staff UI and a JSON {error: total_running_reports_limit_exceeded}
  with HTTP 429 for the JSON endpoints.

Test Plan:
1) Set total_running_reports_per_user_limit to 3 in your koha-conf.xml.
2) Create 3 long-running reports like:
   SELECT COUNT(*) FROM items i1 JOIN items i2 JOIN items i3 JOIN items i4 JOIN items i5 JOIN items i6
3) Run each report in turn by opening a new tab, browsing to the
   reports module, and choosing to run that report.
4) Note the first three continue to run, but a fourth attempt gives
   the new "too many reports running" alert in the staff UI.
5) Repeat against /cgi-bin/koha/svc/report?id=N and confirm an HTTP
   429 response with JSON {error: total_running_reports_limit_exceeded}.
6) Set total_running_reports_per_user_limit back to 0 and confirm
   normal behaviour resumes.
7) prove t/db_dependent/Koha/Reports.t -> 13 tests pass, including a
   new subtest for TotalRunning.

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:21 -06:00
Lucas Gass 158f0bbef5
Bug 41918: (RM follow-up) Add POD
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:21 -06:00
Martin Renvoize 4227119b63
Bug 41918: (QA follow-up) Catch DuplicateRunning in op=export path
The CSV/tab export branch (op=export) of reports/guided_reports.pl
calls Koha::Report::prep_report just like the op=run branch and the
two svc/report endpoints, but unlike the others it had no eval/catch
around the call. The QA follow-ups that pushed enforcement into the
model layer therefore left this one path emitting a 500 (uncaught
Koha::Exceptions::Report::DuplicateRunning) when the configured
duplicate-running limit is exceeded.

Wrap the call in the same pattern as svc/report: on
DuplicateRunning return HTTP 429 Too Many Requests with a plain-text
body explaining the situation. Plain text rather than the staff
template because op=export sets file-download headers and the user is
expecting either the file or an error - we don't have a sensible way
to fall back into the report-run template from this branch.

The CLI runners (misc/cronjobs/runreport.pl, misc/export_records.pl)
still bypass prep_report and therefore the limit, which is intended:
scheduled / batch jobs should not be subject to user-quota
enforcement.

Test plan:
1) Set duplicate_running_reports_per_user_limit=1 in koha-conf.xml,
   restart_all.
2) Start a long-running report from the staff UI.
3) While it is running, navigate to op=export with the same report
   id (eg. via the "Export" button).
4) Before the patch: 500 with a stack trace in the logs.
   After the patch: 429 with body "This report is already running.
   Please wait for it to finish before running it again."
5) Set the limit back to 0 and confirm exports work normally again.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:20 -06:00
Martin Renvoize 63af0dd4fd
Bug 41918: (QA follow-up) Scope processlist query to the current DB user
Koha::Reports->running queries information_schema.processlist. Without
the PROCESS privilege MariaDB/MySQL already restrict the view to the
caller's own threads, so the previous code worked correctly on stock
deployments. However, if a site has granted broader privileges (eg. a
shared MariaDB server where the Koha DB user happens to also have
PROCESS, or a custom role) the query would happily count threads
belonging to other database users - another Koha instance, a reporting
tool, or any unrelated application sharing the server.

Add an explicit

    user = SUBSTRING_INDEX(CURRENT_USER(), '@', 1)

clause so the query is always scoped to the connection's MySQL user,
regardless of what privileges that user has been granted. The
processlist.user column holds just the username with no host, so we
strip the '@host' off CURRENT_USER() before comparing.

The new clause has no bind parameters - it is a constant per
connection - so the existing parameterised-bind assertions in the
running subtest still hold. A small additional assertion verifies the
clause is present in the SQL text.

Test plan:
1) prove t/db_dependent/Koha/Reports.t (now 12 tests, all pass).
2) Optional: in a multi-tenant MariaDB, grant PROCESS to the Koha DB
   user, start a long query as a different DB user with the marker
   /* { saved_sql.id: 1 } { user_id: 1 } */ in its info, then call
   Koha::Reports->running. Confirm the foreign thread is NOT counted.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:20 -06:00
Martin Renvoize 7d12dac853
Bug 41918: (QA follow-up) Enforce duplicate-running limit in the model
The previous patches enforced duplicate_running_reports_per_user_limit
only in reports/guided_reports.pl. Two other code paths run saved SQL
reports for an authenticated user and bypassed the guard entirely:

- svc/report (staff JSON endpoint, eg. used for chart widgets and any
  caller hitting /cgi-bin/koha/svc/report?id=N directly)
- opac/svc/report (OPAC JSON endpoint)

A user who hit their staff-UI limit could still spawn additional copies
of the same long-running report via either svc endpoint, defeating the
guard.

Push the check into Koha::Report::prep_report itself, which is the one
function all three callers go through. When the limit is reached
prep_report now throws a new Koha::Exceptions::Report::DuplicateRunning
(carrying report_id, user_id and the configured limit). The guided
reports CGI catches it and re-renders the existing "report is already
running" alert; both svc/report endpoints catch it and return HTTP 429
Too Many Requests with a JSON { error: "duplicate_running_report" }
payload.

Notes:
- The pre-flight Koha::Reports->running call is removed from the
  guided-reports CGI; the model now owns that policy and the
  controller is just rendering. The TOCTOU window remains identical
  in size and is the same intentional trade-off as before.
- Limit=0 (the default) and absent userenv both bypass the check, so
  CLI cronjobs (misc/cronjobs/runreport.pl) and direct DB users are
  unaffected.

Test plan:
1) prove t/db_dependent/Koha/Reports.t - now includes a new subtest
   covering limit=0 (lives), under limit (lives) and at limit (throws
   Koha::Exceptions::Report::DuplicateRunning).
2) Set duplicate_running_reports_per_user_limit=1 in koha-conf.xml,
   restart_all.
3) Open a long-running report in two staff tabs - second tab shows
   the "report is already running" alert (unchanged behaviour).
4) While one run is in flight, hit /cgi-bin/koha/svc/report?id=N as
   the same user - now returns HTTP 429 with the JSON error payload
   (previously: ran a second copy of the query).
5) Same check on /cgi-bin/koha/opac/svc/report?id=N.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:19 -06:00
Martin Renvoize b4d9063368
Bug 41918: (QA follow-up) Document SQL marker contract and TOCTOU window
Two small documentation improvements following on from the duplicate-
running guard:

- Koha::Report::prep_report now spells out the tracking-comment format
  in its POD. Koha::Reports->running parses this comment, and external
  log/audit tooling that consumes Koha SQL relied on the previous
  format (/* saved_sql.id: <id> */). Calling out the new format
  explicitly makes the public contract visible to anyone reading the
  POD or grepping for it, and warns future contributors that changing
  the wrapper will break running() too.

- reports/guided_reports.pl gets an inline comment explaining the
  TOCTOU window between the count check and the actual report
  execution. The window is intentional - the guard exists to stop
  accidental double-clicks and two-tab submissions, not to be a
  serializing lock - so worst case is one extra concurrent run, which
  is acceptable and cheaper than the alternative.

No behaviour change.

Test plan:
1) prove t/db_dependent/Koha/Reports.t still passes.
2) perldoc Koha::Report -> prep_report section now mentions the
   saved_sql.id / user_id wrapper format.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:19 -06:00
Martin Renvoize c8f0f064d6
Bug 41918: (QA follow-up) Add unit tests for Koha::Reports->running
Adds a 'running' subtest covering the new method:

- Returns a Koha::Reports resultset (object identity, not opaque ids).
- Returns an empty resultset when nothing is in flight, and when the
  user_id / report_id filter does not match any process.
- Parses saved_sql.id markers out of synthetic processlist rows and
  resolves them to real Koha::Report objects, ignoring rows without
  the marker.
- Sends parameterised LIKE binds for user_id (proves the WHERE clause
  is built without string interpolation).
- Falls back to an empty resultset and does not propagate the
  exception when the underlying selectall_arrayref call dies, eg.
  when the database user lacks PROCESS privilege.

To make the failure path mockable without trying to mock DBI::db
(which is not loadable as a separate file), the dbh selectall is
extracted into a tiny Koha::Reports::_processlist_rows helper so
Test::MockModule can replace it cleanly. The helper is otherwise
behaviour-preserving.

Test plan:
1) prove t/db_dependent/Koha/Reports.t -> 11 tests, all pass.
2) Existing prep_report subtest still passes.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:18 -06:00
Martin Renvoize 081e895ccb
Bug 41918: (QA follow-up) Degrade gracefully without PROCESS privilege
Koha::Reports->running queries information_schema.processlist. A MySQL
user without the PROCESS privilege still sees its own threads, but if
the privilege check fails entirely (or the engine returns an unexpected
error) the previous code would let the DBI exception bubble up and
abort the calling report run.

Wrap the selectall in eval and on failure log via Koha::Logger and
treat the result as empty. This means a misconfigured database
degrades to "no enforcement" rather than to "no reports can be run",
matching the principle that the duplicate-running guard is a safeguard,
not a critical path.

The POD now documents this fallback so future readers know the empty
resultset has two possible meanings: nothing is currently running, or
we could not look. The Koha::Logger->warn ensures the latter is still
observable in the configured log destination.

Test plan:
1) prove t/db_dependent/Koha/Reports.t still passes.
2) Optional: revoke SELECT on information_schema.processlist for the
   koha DB user, run a report, observe a warning in the logs and the
   report still executes (with no duplicate enforcement).

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:18 -06:00
Martin Renvoize 521ddaacad
Bug 41918: (QA follow-up) Update prep_report tests for new SQL comment
The first patch in this series changed the SQL comment appended by
Koha::Report::prep_report from

    /* saved_sql.id: <id> */

to

    /* { saved_sql.id: <id> } { user_id: <user_id> } */

but did not update the existing assertions in t/db_dependent/Koha/Reports.t,
leaving three subtest failures in the prep_report subtest. Refresh the
expected strings to match the new format and pick up the userenv-derived
user_id (or 0 when no userenv is in scope, matching prep_report's fallback).

Test plan:
1) Apply this follow-up.
2) prove t/db_dependent/Koha/Reports.t
3) Verify all 10 tests pass (previously 3 subtests of prep_report failed).

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:17 -06:00
Martin Renvoize 1162d2d67c
Bug 41918: (QA follow-up) Refactor Koha::Reports->running
- Fix POD synopsis: invalid syntax and wrong field (userid vs
  borrowernumber) replaced with a correct example and parameter list.
- Build the WHERE clause with parameterised bindings via
  $schema->storage->dbh, removing string interpolation of
  $user_id / $report_id into LIKE patterns and the regex match.
  IDs are formatted with %d so non-integer input becomes 0 and cannot
  inject regex/LIKE metacharacters.
- Return a Koha::Reports resultset instead of a list of MySQL process
  ids. The method name now matches what it returns and callers can use
  the standard ->count / iteration interface. The single in-tree caller
  in reports/guided_reports.pl is updated; the template error key is
  simplified from duplicate_running_report_ids to a boolean
  duplicate_running_report (the ids were never displayed).

Test plan:
1) Apply the previous Bug 41918 patches and this follow-up.
2) Set duplicate_running_reports_per_user_limit to 1 in koha-conf.xml.
3) Restart all the things.
4) Open a long-running saved report in two tabs and run it from both.
5) Verify the second tab shows "This report is already running..."
6) Set the limit to 0 and confirm both tabs run normally.
7) prove t/db_dependent/Reports.t still passes.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:17 -06:00
Kyle M Hall 9d0d9715e8
Bug 41918: Add ability to disable or change limit via config xml
This commit add the option duplicate_running_reports_per_user_limit to
koha-conf.xml. If this option is empty or 0, the feature is disabled. If
this option is enabled, Koha will limit the number of times a given
report can be run of a single Koha user.

Test Plan:
1) Apply this patch
2) Set duplicate_running_reports_per_user_limit to 1, verify previous
   behavior is maintained
2) Set duplicate_running_reports_per_user_limit to 0, verify the feature
   is disabled
3) Set duplicate_running_reports_per_user_limit to 2, verify Koha limits
   the number of duplicates of the report to the number specified in duplicate_running_reports_per_user_limit

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:16 -06:00
Kyle M Hall 7bd64b0f6f
Bug 41918: Prevent a given librarian from running the same report multiple times simultaneously
Test Plan:
1) create a long running report such as:
   SELECT COUNT(*) FROM items i1 JOIN items i2 JOIN items i3 JOIN items i4 JOIN items i5 JOIN items i6
2) Run this report multiple times
3) Note the processes are running via SHOW FULL PROCESSLIST from koha-mysql
4) Kill those processes
5) Apply this patch
6) Restart all the things!
7) Try to run the report in two tabs, note the second tab opened says
   the report is already running!

Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:16 -06:00
Kyle M Hall d0c5ceea3d
Bug 41918: Add method to see running report queries by user and id
Signed-off-by: Alex Carver [Acerock7] <alex@rcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:15 -06:00
Matt Blenkinsop 25a2e938fa
Bug 26355: (follow-up) Fix cypress tests
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:15 -06:00
Jonathan Druart 084c07a0fe
Bug 27826: Fix OPAC/SCO_spec.ts
1. The DELETE in afterEach was not needed
2. The test was failing randomly on the UPDATE of the beforeEach
  `Duplicate entry '+42+' for key 'itembarcodeidx'`
I don't explain it, but this patch should fix the failure.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:14 -06:00
Lucas Gass 4a47cc611f
Bug 41956: DBRev 25.12.00.062
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-07 07:56:07 -06:00
Pedro Amorim 209796ec0b
Bug 41956: Display hold_group_id
Test plan:
1) Enable DisplayAddHoldGroups sys pref
2) Place two title-level holds for the same patron on two different biblios.
3) Group them via the patron's holds page. Note the hold_group_id shown in the table and the URL when you click through to the group page (e.g. hold_group_id=42).
4) Create a second hold group for the same patron with two more holds. Confirm the hold_group_id is shown instead of the previous visual_hold_group_id.
5) Go to Circulation > Holds to pull (/cgi-bin/koha/circ/pendingreserves.pl). Confirm each grouped hold shows 'Hold group 42' as a clickable link, not
   the old 'part of a hold group' text.
6) Go to Circulation > Holds queue (/cgi-bin/koha/circ/view_holdsqueue.pl). Confirm the same — 'Hold group 42' link visible beneath the title.
7) Log in to the OPAC as that patron, go to 'Your summary' > 'Holds' tab. Confirm the 'Hold group' column shows 42.
8) Cancel one hold from the group. Confirm the group still appears on the patron's 'Holds' tab (one hold remains).
9) Cancel the last hold in the group. Confirm the no holds of the group and the group no longer appear on the patron's 'Holds' tab. Check holds history on the Staff UI:
(/cgi-bin/koha/members/holdshistory.pl?borrowernumber=NNN) — both cancelled holds should still show 42 in the 'Hold group ID' column.

Run tests:
prove t/db_dependent/Reserves/HoldGroup.t
prove t/db_dependent/Koha/Hold.t
prove t/db_dependent/Koha/Patron.t
prove t/db_dependent/api/v1/patrons_hold_groups.t
prove t/db_dependent/api/v1/holds.t
prove t/db_dependent/api/v1/patrons_holds.t

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:21:00 -06:00
Pedro Amorim 5d716d1e28
Bug 41956: Remove visual_hold_group_id
Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:21:00 -06:00
Lucas Gass 8dab161af0
Bug 41983: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:59 -06:00
Lucas Gass 4c9805f960
Bug 41983: DBRev 25.12.00.061
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:59 -06:00
Pedro Amorim 0f7ccaa8ae
Bug 41983: (QA follow-up): Fix atomicupdate hold_group_id column
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:58 -06:00
Pedro Amorim 2ed777a854
Bug 41983: (QA follow-up): Rebuild the holds queue when groups are edited
Test plan (ktd)
0) Update the database (ktd, ignore if sandbox):
  $ updatedatabase
1) Enable 'RealTimeHoldsQueue' and 'DisplayAddHoldGroups' system preferences
2) restart_all # This is required or the worker won't pick up the new sys pref values
3) Place at least 2 holds for a patron on different items/biblios, without grouping them:
  <staff_url>/cgi-bin/koha/members/moremember.pl?borrowernumber=19
4) Build the holds queue once to get a baseline:
  $ perl misc/cronjobs/holds/build_holds_queue.pl --force
5) Visit the holds queue and confirm the holds appear without a group indicator:
  <staff_url>/cgi-bin/koha/circ/view_holdsqueue.pl?branchlimit=&itemtypeslimit=&run_report=1
6) Create a hold group for those holds via the patron page
 <staff_url>/cgi-bin/koha/circ/circulation.pl?borrowernumber=<patron_borrowernumber>
7) Without running build_holds_queue.pl, refresh the holds queue, confirm the 'part of a hold group' indicator now appears
8) Ungroup the holds, refresh the holds queue, confirm the 'part of a hold group' is no longer showing
9) Run the unit tests:
  $ prove t/db_dependent/Reserves/HoldGroup.t

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:58 -06:00
Pedro Amorim af95e03c08
Bug 41983: Display in UI
Test plan:
0) Update the database (ktd, ignore if sandbox):
$ updatedatabase
1) Enable DisplayAddHoldGroups sys prefs
2) Go to 'Henry' and click 'Search to hold':
<staff_url>/cgi-bin/koha/members/moremember.pl?borrowernumber=19
3) Search for 'test' and click the top 'Select all' link
4) Click the downward arrow in the 'Place hold' button and pick 'Place hold for Henry'
5) Set a pickup location for each hold and tick the 'Treat as hold group' checkbox
6) Click 'Place holds'.
7) Run build_holds_queue.pl:
perl misc/cronjobs/holds/build_holds_queue.pl --force
8) Visit the Holds queue page:
<staff_url>/cgi-bin/koha/circ/view_holdsqueue.pl?branchlimit=&itemtypeslimit=&run_report=1
9) Confirm the 'part of a hold group' bit shows and display the modal like 'Holds to pull'

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:57 -06:00
Pedro Amorim a4f5cc19a1
Bug 41983: Add hold_group_id column to tmp_holdsqueue table
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:57 -06:00
Pedro Amorim 107123fd07
Bug 41983: Insert hold_group_id when creating tmp_holdsqueue
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:56 -06:00
Owen Leonard b8832e7d7e
Bug 42439: Remove event attributes from label-edit-batch.tt
This patch removes an "onload" event attribute from the label batch edit
template. An "onload" event attribute on the body tag can be easily
moved to the $(document).ready block.

To test, apply the patch and go to Cataloging -> Label creator -> Label
batches -> New label batch. As soon as the page loads the "Add by
barcodes" textarea should get focus.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:56 -06:00
Owen Leonard 3160983a6d
Bug 42438: Remove event attributes from icon selection include file
This patch removes an "onmousedown" event attribute from the include
file used by item types and authorized values administration in icon
selection. The event handling is copied to each page's JS block.

To test, apply the patch and go to Administraion -> Item types.

In the "Choose an icon" section, click the "Remote image" tab. Click
the text input field. The corresponding radio button should be
checked.

Perform the same check on Administration -> Authorized values.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:55 -06:00
Nick Clemens c85f9a02a9
Bug 42424: Javascript error prevents saving when an instance of an 'important' or 'required' subfield is deleted
In the loop to check fields, we enter a conditional when the element is null - but then we try to adjust the element.
This patch removes the condition and prevents accessing NULL fields

To test:
 1 - Go to Administration->MARC Bibliographic framework
 2 - Under Default click Action->MARC Structure
 3 - Search for 020
 4 - Click Actions->Edit subfields
 5 - Click on tab 'a'
 6 - Check important, save
 7 - Cataloging->New from Z39
 8 - Search for ISBN: 9798225048921 (or any record you want)
 9 - Ensure the result has more than one ISBN and click Actions->Import
10 - Delete one of the ISBN fields
11 - Click 'Save'
12 - Check the console (F12)
     Uncaught TypeError: Cannot read properties of null (reading 'nodeName')
13 - Apply patch
14 - Repeat 8-10
15 - Click save (and clear any other fields that need edited)
16 - Confirm record can be saved

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:55 -06:00
Lucas Gass 2aec8ef17e
Bug 41768: Tidy suggestion.tt
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:54 -06:00
Jonathan Druart f1c3c2d89a
Bug 41769: Translate "%s, by %s"
Instead of translating ", by" we add more flexibility for languages that
need to invert author and title for instance.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:54 -06:00
Ayoub Glizi-Vicioso 97b4b2f1db
Bug 41769: Make ', by' string translatable in suggestions
This fixes a translation issue in the purchase suggestions
table in the staff interface - a ", by" is added between the
title and the author in the suggestion column, the "by" is
not translated when the language for the staff interface
is a language other than English.

Test plan:

1. Add another language to Koha:
   ./misc/translator/translate install fr-CA

2. Enable the new language in StaffInterfaceLanguages:
   2.1. Go to Administration > System preferences
   2.2. Search for StaffInterfaceLanguages
   2.3. Check the new language
   2.4. Click Save all I18N/L10N preferences

3. Create a new purchase suggestion:
   3.1. Go to More > Suggestions
   3.2. Click New purchase suggestion
   3.3. Fill out the title AND the author at least
   3.4. Click Submit your suggestion

4. View the suggestion in the table
   --> It says title, by author

5. Switch language
   --> The ", by" is still in English

6. Apply patch.

7. Run these commands to update the translation:
   7.1 ./misc/translator/translate update fr-CA
   7.2 ./misc/translator/translate install fr-CA

8. Reload page.

9. Notice that ", by" is now translated.
   --> in this instance, you will see ", par"

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:53 -06:00
Lucas Gass e13e22f5bf
Bug 39107: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:53 -06:00
Lucas Gass 2b1425a80e
Bug 39107: DBRev 25.12.00.060
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:52 -06:00
Emily Lamancusa 4a57c1f779
Bug 39107: (QA follow-up) Use existing foreign_key_exists function
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:52 -06:00
Saiful Amin fe24b5030e
Bug 39107: Remove illrequests_safk FK constraint on illrequests.status_alias as required by MySQL 8.4+
MySQL 8.4 requires foreign keys to reference PRIMARY KEY or UNIQUE keys.
Since authorised_values.authorised_value is only unique in combination
with category (composite UNIQUE key av_uniq), the FK constraint is not
compatible with MySQL 8.4+.

Data integrity for illrequests.status_alias is maintained at the application layer:
- Koha::ILL::Request->status_alias() validates values against
  authorised_values (category='ILL_STATUS_ALIAS') before storing
- All ILL request operations in Koha use this object layer
- CASCADE behaviors (ON DELETE SET NULL, ON UPDATE CASCADE) are already
  handled by the application layer when modifying/deleting authorised values.

Test Plan, using KTD:
1. Prepare KTD for MySQL 8.4
    - In koha-testing-docker, edit 'compose/db-mysql.yml'
    - Comment out the line 5:
    `# command: --default-authentication-plugin=mysql_native_password`

    Note: This is required because MySQL 8.4 doesn't support the native password plugin.

2. Start a new instance with MySQL 8.4
    - `DB_IMAGE=mysql:8.4 ktd up`
    - Observe that it fails to start with the errors such as 'DBD::mysql::st execute failed'

3. Apply the patch
    - `git bz apply 39107`

4. Start the instance again
    - `DB_IMAGE=mysql:8.4 ktd up`
    - Observe that it starts successfully

5. Update the database (Optional, only for existing instances)
    - `perl installer/data/mysql/updatedatabase.pl`

6. Access Koha Staff and OPAC interfaces and verify that everything works as expected

7. Sign off

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 15:20:44 -06:00
Lucas Gass 04a3371ee9
Bug 42103: (follow-up) Fix failing test
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 09:52:19 -06:00
Matt Blenkinsop c23b2fb2f7
Bug 26355: (follow-up) Add a fallback value for patronValues in insertSamplePatron
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 09:52:18 -06:00
Lucas Gass ce92e707e1
Bug 37671: (follow-up) Add INSERT IGNORE to db_rev
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 09:52:18 -06:00
Martin Renvoize dfb875154d
Bug 42188: (follow-up) Update ModBiblio test for new info format
The 'ModBiblio on invalid record' subtest asserted the action log info
column contained 'biblionumber', but Koha::Biblio::_unblessed_for_log
restricts the logged hash to frameworkcode, opac_suppressed, and serial.
The biblionumber lives in action_logs.object, not in the info JSON.

Update the regex to match the new "biblio { ... }" prefix format set by
C4::Log::logaction so the assertion confirms biblio data is logged
without depending on a field that is intentionally excluded.

Test plan:
1. prove t/db_dependent/Biblio.t

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-06 09:52:11 -06:00
Lucas Gass 08a27b1ab1
Bug 29016: (RM follow-up) Add action_logs tag to swagger def
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:12 -06:00
Martin Renvoize cd5e4d22a1
Bug 29016: (QA follow-up) Document the action_logs list controller
Replace the empty =head3 stub with a proper synopsis explaining the URL,
the supported filtering / ordering / embed surface, and the permission
requirement. Aligns the controller with the documentation density of
other Koha REST controllers and removes the "Missing POD" QA noise.

Test plan:
  1. perl -MKoha::REST::V1::ActionLogs -e ''  (still loads)
  2. perl -c Koha/REST/V1/ActionLogs.pm        (still parses)
  3. perldoc Koha/REST/V1/ActionLogs.pm        (renders the new POD)

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:11 -06:00
Martin Renvoize c6a8634ff5
Bug 29016: (QA follow-up) Restore biblio<->item log expansion
The catalog detail page links to the modification log with
modules=CATALOGUING&object_type=biblio&object=<biblionumber>. The old
server-rendered viewer matched both biblio-level entries (object =
biblionumber, info LIKE 'biblio%') and item-level entries (object IN
(itemnumbers of this biblio), info LIKE 'item%'). Converting to the
AJAX DataTable kept that expansion only in the CSV export branch, so
the on-screen view silently dropped item-level history.

Recreate the expansion in the AJAX path:

  * tools/viewlog.pl: when (single CATALOGUING module + object_type
    =biblio + non-empty object) collect the biblio's itemnumbers and
    expose them, along with object_type, to the template.
  * viewlog.tt: include object_type and biblio_itemnumbers in the
    search_params JSON the inline script builds for the JS.
  * viewlog.js: when those params are present, build the same -or
    clause the old SQL produced and skip the regular per-object filter.
    The info substring filter is also suppressed in that branch (the
    old code did the same: it dropped %info% in favour of the -or).

Test plan:
  1. Enable CataloguingLog.
  2. Edit the biblio and a couple of its items.
  3. From the biblio detail page, open "Modification log".
  4. Verify the table shows both the biblio MODIFY rows and the item
     MODIFY rows for that biblio (and only those).
  5. From Tools -> Log viewer, plain Cataloguing filter still returns
     the unfiltered log set as before.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:10 -06:00
Martin Renvoize 19df8f3dfc
Bug 29016: (QA follow-up) Translate circulation overrides in info column
CIRCULATION rows whose info payload is a JSON object can carry `forced`
and `confirmations` arrays listing the override codes a librarian
acknowledged when checking out, renewing or otherwise circulating an
item (e.g. DEBT, AGE_RESTRICTION, BIBLIO_ALREADY_ISSUED). The old
server-rendered template ran these through the translate_log_override
SWITCH block in action-logs.inc; in the new AJAX-driven viewer the JS
default branch was just escapeHtml()-ing the raw JSON, leaving the
codes unlocalised and unreadable.

Port the override -> human-readable label map into str/viewlog.inc as
override_labels, mirroring the module/action/interface label maps
already there. Add a renderCircInfo() helper that recognises the JSON
shape and emits the same div.forced / div.confirmed blocks the old
template did, so the existing CSS continues to apply.

renderInfo() now falls through CIRCULATION -> renderCircInfo, the
generic record-info renderer (CATALOGUING / AUTHORITIES from bug 42032)
and finally escapeHtml(), so non-JSON payloads (item barcodes,
ID-only info, ...) keep displaying as before.

Test plan:
  1. Enable IssueLog.
  2. Trigger an override checkout (e.g. patron with debt, item not for
     loan) and confirm.
  3. Tools -> Log viewer, filter by module=Circulation.
  4. The override row's Info column should show one or more localised
     "Patron had debt", "Item was not for loan", ... lines instead of
     a raw JSON blob.
  5. Plain ISSUE / RETURN rows (no overrides) keep showing the raw
     itemnumber / barcode payload as before.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:10 -06:00
Martin Renvoize 4710257c21
Bug 29016: (QA follow-up) Render embedded patron, fix JS i18n drift
QA review pointed out three regressions and a label gap in the new
DataTable-driven log viewer:

  1. The Librarian column showed a bare borrowernumber linked to the
     patron page; the old server-side template showed the librarian's
     full name with the borrowernumber in parentheses.

  2. The Object column for MEMBERS / CIRCULATION / FINES / APIKEYS rows
     showed only the borrowernumber; the old template showed the
     patron's full name and used "API keys for <name> (<id>)" wording.

  3. Several user-visible strings in renderObject() were hard-coded
     English ("Bibliographic record ", "Subscription ", "Authority ",
     "Basket ") and would never have been picked up by the translator.

  4. The form's Actions list still includes RESTORE but the new
     str/viewlog.inc action_labels map missed the corresponding entry,
     so the rendered Action cell fell through to escapeHtml("RESTORE")
     instead of the localised "Restore".

This patch:

  * Asks the API to embed both `librarian` and `patron` and uses those
    objects when rendering the Librarian and Object columns. When the
    embed is absent (e.g. because the relation does not match for a
    non-MEMBERS row) the renderers fall back to the bare id, preserving
    backwards compatibility.

  * Wraps the four hardcoded strings in __() with %s placeholders so
    the translator extracts them. The Basket markup is also assembled
    via __("Basket %s").format(...) rather than string concatenation
    so that translators can re-order the link text.

  * Adds RESTORE: _("Restore") to str/viewlog.inc.

Test plan:
  1. Tools -> Log viewer with various modules selected.
  2. Verify the Librarian column shows "Firstname Surname (id)" and
     links to the moremember page.
  3. Filter to MEMBERS / CIRCULATION / FINES; verify the Object column
     shows the patron's full name, and the API keys row uses the
     "API keys for <name> (<id>)" wording.
  4. Filter to CATALOGUING / SERIAL / AUTHORITIES / ACQUISITIONS; verify
     "Bibliographic record N", "Subscription N", "Authority N" and
     "Basket N" still link correctly.
  5. Restore a deleted biblio (CataloguingLog must be on); verify the
     Action column shows "Restore" rather than "RESTORE".

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:09 -06:00
Martin Renvoize e7eb27d48e
Bug 29016: (QA follow-up) Expand action_logs API and test coverage
Build on the new ActionLog DBIC relations to expose them as opt-in
embeds, and shore up gaps in the test coverage that QA flagged.

Swagger
  * paths/action_logs.yaml declares an x-koha-embed header parameter
    constrained to the supported relations (librarian, patron).
  * definitions/action_log.yaml adds the corresponding optional
    `librarian` and `patron` object fields.

Tests
  * Convert the single 24-test subtest into focused subtests:
    - authorization (403 vs 200)
    - simple typed filters
    - q-DSL: multi-value module, info LIKE, combined filter
    - embeds: librarian and patron, with and without x-koha-embed
    - pagination + X-Total-Count and order_by
  * Mock AuthSuccessLog/AuthFailureLog/BorrowersLog to 0 to remove the
    flakiness where the API GET itself appended an AUTH log row between
    delete and assertion.
  * Build the test librarian with both `tools` and `borrowers` flags so
    embedded patron fields are not redacted by Koha::Patron->is_accessible.
  * Use a small build_fixtures() helper to avoid copy-pasting setup
    across subtests.

Test plan:
  1. yarn build && restart_all  (refresh swagger bundle)
  2. prove t/db_dependent/api/v1/action_logs.t  (all subtests green)
  3. curl -H 'x-koha-embed: librarian,patron' \
       /api/v1/action_logs?module=MEMBERS  -- patron and librarian
       objects appear in the response.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:09 -06:00
Martin Renvoize c963591c06
Bug 29016: (QA follow-up) Add librarian/patron relations to ActionLog
QA review noted that the new /api/v1/action_logs endpoint exposed only
column data, so the log viewer ended up showing bare borrowernumbers in
the Librarian and Object columns where the old server-side rendering
showed firstname/surname (borrowernumber). Add the DBIC plumbing needed
to support x-koha-embed for those relationships.

Two belongs_to relationships are declared below the auto-generated
marker on Koha::Schema::Result::ActionLog:

  * librarian -> borrowers.borrowernumber via action_logs.user
  * patron    -> borrowers.borrowernumber via action_logs.object

The patron relation is intentionally polymorphic; the action_logs.object
column holds different identifiers per module (biblionumber for
CATALOGUING, subscriptionid for SERIAL, ...) so callers must filter by
module before trusting the joined patron - this is documented in both
schema and Koha::ActionLog comments.

Koha::ActionLog gains explicit librarian() and patron() accessor methods
following the Koha::Hold pattern, returning Koha::Patron objects (or
undef when the relation does not match). The accessors deliberately
avoid `use Koha::Patrons` at compile time: Koha::ActionLog is loaded
very early via C4::Log and Koha::Patron is not yet defined at that
point - the accessors reach for it lazily at call time.

koha_object_class / koha_objects_class overrides are also added to bring
the result class in line with the rest of the codebase.

Test plan:
  1. prove t/db_dependent/api/v1/action_logs.t  (still green)
  2. perl -MKoha::ActionLog -e 'print "ok"' (no circular import)

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:08 -06:00
Kyle M Hall 5525953c01
Bug 29016: Move strings from js file to tt file
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-05 13:42:08 -06:00
Kyle M Hall c861e2dafe
Bug 29016: Convert log viewer to ajax datatable
The script viewlog.pl it's very slow because it retrieves all action logs at the same time.
It should use KohaTables with pagination/number max of returning entries like other Koha tables (API + AJAX wrapper).

Test Plan:
1) Apply this patch
2) Rebuild swagger file
3) Restart all the things!
4) Browse to /cgi-bin/koha/tools/viewlog.pl
5) Test functionality, functionality should be no different!
   * Limit by librarian
   * Limit by modules
   * Limit by actions
   * Object search string
   * Info search string
   * Limit by interface
   * Limit by from date
   * Limit by to date
   * Limit by from and to date
   * Test "Compare" checkboxes ( only two should be selectable at once )
   * Test column sorting
   * Test sharable link
   * Test output to a file
   * Test columns show/hide

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-05 13:42:07 -06:00
Kyle M Hall 4edc85cf29
Bug 29016: Add unit tests
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-05 13:42:07 -06:00
Kyle M Hall cfa349ccaf
Bug 29016: Add REST API for action logs
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-05 13:42:06 -06:00
Martin Renvoize 4cead7f389
Bug 42032: (follow-up) Eliminate phantom indicator diffs on authority MODIFY
Authority MODIFY action log diffs were emitting bogus entries for every
field with a default ' ' indicator on every save, producing output like:

    _marc.fields.1.040.ind1   ' '   ' '
    _marc.fields.1.040.ind2   ' '   ' '
    _marc.fields.2.150.ind1   ' '   ' '
    _marc.fields.3.667.ind1   ' '   ' '
    ...

even when only a single subfield was edited.

Root cause: Struct::Diff treats a utf8-flagged ' ' as different from a
non-utf8 ' ' SV, even though Perl's eq returns true. ModAuthority was
sourcing the two sides of the diff from different parse paths:

  - $oldrecord = GetAuthority($authid)
      -> MARC::Record->new_from_xml(...) -> indicators arrive utf8-flagged
  - $record (the "after" side) was the in-memory MARC built by the
      editor from CGI parameters -> indicators are bare byte strings

The XML round-trip through AddAuthority normalises the stored copy, but
the in-memory $record we passed to logaction had not been re-parsed, so
every default indicator looked like a change to Struct::Diff.

Fix: after AddAuthority commits the new state, re-read it via
$new_authority->record and pass that to _authority_log_data. Both sides
of the diff now traverse the same new_from_xml path, utf8 flags match,
and Struct::Diff falls silent on unchanged indicators.

This is the symmetric counterpart of how biblios already work in
C4::Biblio: ModBiblio sources both $original_marc and $updated_marc_log
through Koha::Biblio::Metadata::record_strip_nonxml (which also calls
new_from_xml), so biblios don't hit this.

The deeper asymmetry between in-memory editor MARC and stored MARC
(default indicators only get normalised on the XML round-trip) is left
for a separate bug; this patch keeps the action log diff clean without
touching the storage path.

Test plan:
1. prove t/db_dependent/AuthoritiesMarc.t
2. Enable AuthoritiesLog. Edit any authority via the staff UI making a
   single subfield change. Open Tools -> Log viewer for that authority
   and verify the diff column lists only the changed subfield (plus the
   005 timestamp), with no rows for ind1/ind2 of untouched fields.
3. Re-run the same edit a second time changing nothing else; the diff
   should again be limited to 005 + the touched subfield.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:06 -06:00
Martin Renvoize 9836498611
Bug 42032: (follow-up) Pretty-render authority info column in log viewer
Mirrors bug 42188's biblio log-viewer rendering. Generalise the
biblio-only renderCatalogInfo() helper in viewlog.js to also match
the "authority <JSON>" prefix introduced by the previous follow-up,
and rename it to renderRecordInfo() to reflect its wider scope. The
existing fmtJsonValue() helper already handles MiJ's nested arrays
of subfield objects, so no rendering changes are needed beyond the
regex and name.

Test plan:
1. Enable AuthoritiesLog.
2. Create, edit, and delete an authority via the staff UI.
3. Go to Tools -> Log viewer, filter by module=Authorities.
4. Verify:
   - ADD    row's info cell shows just "authority".
   - MODIFY and DELETE rows' info cells render as a nested list
     (auth_header columns plus _marc leader + fields array).
   - Diff column still renders the before/after struct-diff table.
5. Re-check a biblio (CataloguingLog) entry: still renders as before.
6. Re-check a MEMBERS entry: untouched.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:05 -06:00
Martin Renvoize 55e2810aac
Bug 42032: (follow-up) Write state JSON to AUTHORITIES info column
Mirrors bug 42188's biblio info-column convention. Generalise the
CATALOGUING-only info-column rewrite branch in C4::Log::logaction to
cover AUTHORITIES as well, using a module-aware prefix map so each
module gets its own prefix ("biblio" / "authority"). For AUTHORITIES
this means MODIFY now stores "authority {pre-change JSON}" and DELETE
stores "authority {final-state JSON}", while ADD stays bare
"authority" (the call site passes the string directly and ADD is
guarded anyway). The biblio behaviour is preserved bit-for-bit.

Test plan:
1. prove t/db_dependent/AuthoritiesMarc.t
2. Enable AuthoritiesLog. Create / modify / delete an authority via
   the staff UI, then check action_logs:
   - ADD    row's info column reads just "authority"
   - MODIFY row's info column reads "authority { ... pre-change JSON ... }"
   - DELETE row's info column reads "authority { ... final-state JSON ... }"
3. prove t/db_dependent/Koha/Biblio.t
4. prove t/db_dependent/Log.t

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:05 -06:00
Martin Renvoize 3ca746f4fe
Bug 42032: (follow-up) Use MARC-in-JSON for authority action log diffs
Mirrors the bug 42188 main commit for biblios. Replace the custom
_marc_record_to_diffable helper with MARC::Record::MiJ's to_mij_structure
so AUTHORITIES action log diffs use the same MARC-in-JSON shape as
CATALOGUING diffs. Add Koha::Authority::_unblessed_for_log to expose
only the auth_header columns that exist independently of the MARC
record (authtypecode, origincode, linkid), avoiding duplication of
MARC-derived data (heading, authtrees), raw marcxml, and system
timestamps. The explicit whitelist also removes the need for the
post-hoc empty-field stripping added in a previous follow-up.

Test plan:
1. prove t/db_dependent/AuthoritiesMarc.t
2. Enable AuthoritiesLog. Create, edit, and delete an authority via
   the staff UI, then inspect action_logs.diff in the DB:
   - _marc contains MiJ shape { leader: "...", fields: [ { tag: {...} }, ... ] }
   - Changes to heading fields produce diffs on
     _marc.fields[N].TAG.subfields[0].a
   - The diff no longer carries a redundant top-level heading entry
     (it's MARC-derived and now only appears in _marc).
3. prove t/db_dependent/Log.t
4. prove t/db_dependent/Koha/Authority.t
5. prove t/db_dependent/Koha/Biblio.t

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:04 -06:00
Lisette Scheer a489907260
Bug 42032: (QA follow-up) fix pod warnings
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-05 13:42:04 -06:00
Martin Renvoize 8e0aede250
Bug 42032: (follow-up) Fix QA issues and strip blank fields from authority logs
- Declare \$deleted_marc outside conditional to satisfy perlcritic
- Remove unused \$original_marc variable
- Strip undef and empty fields from _authority_log_data to avoid blank
  linkid/heading/origincode/authtrees entries in ADD and DELETE logs

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:03 -06:00
Martin Renvoize 79b42fec90
Bug 42032: Include MARC field changes in AUTHORITIES diff logs
The AUTHORITIES action log entries for authority operations previously
stored minimal data: ADD and DELETE logged just the string "authority",
while MODIFY logged a raw MARC-formatted dump of the pre-change record
as a plain text string ("authority BEFORE=>..."). None of these populated
the diff column.

This patch updates AddAuthority, ModAuthority and DelAuthority in
C4/AuthoritiesMarc.pm to follow the same pattern introduced for biblios
in bug 40135: passing combined hashrefs to logaction so that the diff
column is automatically populated with Struct::Diff JSON.

The combined hashref contains the auth_header table columns (authtypecode,
heading, origincode, etc. — excluding the raw marcxml blob) plus a _marc
key built by the new _marc_record_to_diffable helper, which converts a
MARC::Record into a tag => array-of-formatted-strings structure readable
by the existing diff viewer.

A new _authority_log_data helper packages the authority unblessed data
and MARC record into the combined hashref, keeping the three call sites
concise.

For ADD, the logaction is now emitted after the authority record has been
fully stored (after _after_authority_action_hooks), so the diff captures
the complete saved state. For MODIFY, the before-state is captured from
Koha::Authorities before calling AddAuthority, and the after-state is
read back once AddAuthority has committed the new data. For DELETE, the
MARC is captured before the authority is removed.

The diff column for MODIFY will now show both authority table column
changes (e.g. heading) and MARC field-level changes (e.g. _marc.151.0)
in the existing diff viewer table in the log viewer.

Tests are added to verify that ADD, MODIFY, and DELETE all populate the
diff column and that _marc content is captured correctly.

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:03 -06:00
Martin Renvoize 4bd9e7836b
Bug 42188: (follow-up) Pretty-render biblio info column in log viewer
The CATALOGUING info column for MODIFY/DELETE logs stores "biblio "
followed by a pretty-printed JSON dump of the biblio hashref, which
includes a MARC-in-JSON _marc structure. Raw, this is noisy and hard
for librarians to read in the log viewer.

Extract the nested-object formatter already used by the struct-diff
renderer into a shared fmtJsonValue() helper, and use it from a new
renderCatalogInfo() that runs over each div.loginfo on page load. If
the cell text matches "biblio {JSON}" it is replaced with the pretty
rendering; otherwise the cell is left untouched so non-catalog info
payloads (and the bare "biblio" logged on ADD) are unaffected.

Falling back silently on a parse failure means the old non-JSON
Dumper-format item payloads continue to display as-is.

Test plan:
1. Enable CataloguingLog.
2. Create, edit, and delete a biblio record via the staff UI.
3. Go to Tools -> Log viewer, filter by module=Cataloguing.
4. Verify:
   - ADD row's info cell shows just "biblio".
   - MODIFY and DELETE rows' info cells render as a nested list
     (leader, fields array of tag/indicator/subfield objects, plus
     frameworkcode/opac_suppressed/serial).
   - Diff column still renders the before/after struct-diff table.
5. Check another module's log entries (e.g. MEMBERS) are untouched.

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 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
Lucas Gass 7c7258b9da
Bug 26355: (RM follow-up) force DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:07 -06:00
Lucas Gass 3e7626d4e0
Bug 26355: (RM follow-up) Adjust number of tests
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:06 -06:00
Lucas Gass 3fa81b71b4
Bug 26355: DBRev 25.12.00.056
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:06 -06:00
Martin Renvoize 9487953932
Bug 26355: (QA follow-up) Strip self_renewal_available in EmailPatronRegistrations tests
to_api now returns the derived self_renewal_available field, so
reusing a to_api'd patron as the POST body for /api/v1/patrons fails
with 500 (the value has no matching DB column). Adjacent subtests
already delete this read-only field before POSTing; do the same in
the EmailPatronRegistrations subtest.

Test plan:
1. prove t/db_dependent/api/v1/patrons.t — all tests pass.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:05 -06:00
Martin Renvoize e625b68bc1
Bug 26355: (QA follow-up) Bump test plan for new subtest
The "identify_updated_extended_attributes" subtest was added without
bumping the top-level plan, so prove reported a "Bad plan" failure
even though every subtest passed.

Test plan:
1. prove t/db_dependent/Koha/Patron.t — all tests pass, no plan
   mismatch.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:05 -06:00
Martin Renvoize 0cdf8a74c8
Bug 26355: (QA follow-up) Add CSP nonce to inline module script
The forbidden_patterns QA check requires <script> elements to have
either a "src" or "nonce" attribute. The inline module script that
hydrates the Vue island in opac-user.tt was missing a nonce.

Test plan:
1. Run the QA tool against opac-user.tt; forbidden_patterns should
   no longer report the inline <script> on line 1024.
2. Load the OPAC user summary page and confirm the Vue island still
   hydrates (no CSP violation in the browser console).

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:04 -06:00
Matt Blenkinsop bce9aad974
Bug 26355: (follow-up) Fix handling of extended attributes
Extended attributes weren't being identified as changes and so need to be checked to see if any of them have changed

Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:04 -06:00
Andrew Fuerste Henry da92eaa4d3
Bug 26355: (QA follow-up) perl tidy to address qa tool errors
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:03 -06:00
Matt Blenkinsop 029a4f71ec
Bug 26355: (follow-up) Allow the 'Clear' button to remove empty messages
Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:03 -06:00
Matt Blenkinsop cb9e70f8c2
Bug 26355: (follow-up) Allow the setting of multiple information messages
Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:02 -06:00
Matt Blenkinsop a76eb18bed
Bug 26355: Add unit tests
Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:02 -06:00
Matt Blenkinsop d04923b3b9
Bug 26355: Add cypress tests
Adds categories to the db insertion mechanics and allows values to be passed to the patron insertion method

Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:01 -06:00
Matt Blenkinsop e26310e14b
Bug 26355: Update the OPAC templates to support the Vue island
Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:01 -06:00
Matt Blenkinsop 01f5214f0f
Bug 26355: Add the class methods and endpoint to handle renewal
This is part of a sponsored piece of development from one of our customers. There was a specification of requirements but I was conscious of the ideas already discussed on this bug and wanted to try and develop a solution that would balance both sets of requirements. The concept works as follows:
- Self-renewal can be enabled and controlled at a category level
    - The window within which it is available is customisable (both pre and post-expiry)
        - Defaults to the NotifyBorrowerDeparture syspref
    - The amount in fines that block self-renewal is customisable
        - Defaults to the category level charges and then the noissescharge preference
    - The message to display if renewal fails is customisable
- Libraries can also add information to display to the patron at the start of the process.
- The self-renewal process will also respect the OPACPatronDetails and AutoApprovePatronProfileSettings sysprefs. If OPACPatronDetails is enabled, the renewal will redirect the patron to the OPAC details form and ask them to confirm their details are still correct. Any changes will be logged as patron modifications and approved in line with the auto approval syspref
- Renewal happens regardless of whether OPACPatronDetails is enabled, only the details changes are logged as modification requests
- Notifications are sent to the patron in line with their messaging preferences

Test plan:
1. Apply all patches and reset_all
2. In the intranet, navigate to patron categories and edit the ‘Patron’ category
3. Scroll to the bottom, there should be a section called ‘Account expiry and self-renewal’
4. Set ‘Allow self-renewal’ to Yes
5. Set Self-renewal availability period to be 10 days
6. Set Self-renewal availability post-expiry’ to also be 10 days and save
7. In system preferences, set the OPACPatronDetails preference to ‘Don’t allow’
8. Choose a patron that has the ‘Patron’ type category
9. Edit that patron so that you can log into the OPAC and set their expiry date to be yesterday’s date
10. Log into the OPAC as that patron (it may be easiest to do this in incognito as we will be swapping between the intranet and OPAC throughout the test plan and it will remove the need to keep logging out and in)
11. You should see a message that says “You are eligible for self-renewal. Please click here to renew your account.”
12. Click that link - you will be presented with a modal that says “Are you sure you want to renew your account?”
13. Click “Close” - we don’t want to complete renewal yet as there is more functionality available to test
14. Navigate to the patron category again, set ‘Self-renewal availability post-expiry’ to be 0
15. Refresh the OPAC, the self-renewal link should not be there as we are now outside the patron self-renewal window
16. Undo that change in the patron category, the link should reappear when refreshing the OPAC
17. Now set the patron’s expiry date to be tomorrow
18. In the category, set ‘Self-renewal availability period’ to be blank
19. Refresh the OPAC, it should still show the link as it is defaulting to NotifyBorrowerDeparture
20. Set NotifyBorrowerDeparture to 0
21. Refresh the OPAC, the link should vanish
22. In the category, restore ‘Self-renewal availability period’ to 10 days
23. Refresh the OPAC, the link should reappear
24. In the intranet, set the patron to be debarred using a manual restriction
25. In the OPAC refresh the page - self-renewal should no longer be available
26. Remove the restriction - the renewal link will be visible again
27. Set a fine on the patron record of 6 USD
28. Refresh the OPAC, the self-renewal link will be gone because we are over the global ‘noissuescharge’ preference value of 5
29. In the category, set ‘Checkout charge limit’ to 7
30. Refresh the OPAC, the link will be visible because we are now under the category charge
31. Add an additional fine of 2 to the patron account
32. Refresh the OPAC, the self-renewal link is gone
33. In the category, set ‘Self-renewal charge limit’ to be 10
34. Refresh the OPAC, the link will be visible again
35. In the category, set ‘Self-renewal failure message’ to be whatever you want
36. In the OPAC, refresh the page and then click into the self-renewal process
37. This time, click “No” - you should see the failure message you have just set
38. Click “Close” and refresh the page to reset this message
39. In the category, set a message in the “Self-renewal information message”
40. In the OPAC, refresh the page and then click the renewal link again
41. You should be met with the modal, this time displaying the information message we just set up in the patron attributes
42. Click “Yes” - you should see the “Are you sure you want to renew your account?” message again
43. This time when prompted with the “Are you sure” message, click “Yes”
44. The page should refresh and display a confirmation with the new expiry date.
45. In the intranet, view the patron’s record - the date should have been updated
46. In the Modification log tab, there should be a log entry for “Membership renewed”
47. Change the patron’s expiry date again, this time to be yesterday’s date
48. In the OPAC, click on “Personal detail”, the new expiry date should show there
49. Now set the OPACPatronDetails syspref to be “Allow”
50. Click on “Summary” again, the self-renewal link should be there again
51. Click into the self-renewal process and click “Yes” on your verification step
52. This time you should be met with a message that says “Confirm your account details”
53. Click “Continue” - you will be redirected to the OPAC details form
54. There should be a message at the top that says “Please verify your details to proceed with your self-renewal.”
55. Update a field somewhere on the patron form
56. Scroll to the bottom - the button should say “Submit renewal request”
57. Click this button
58. You will be shown the renewal confirmation message
59. In the intranet, on the homepage you should now see a modification request
60. Check the request, it should match the fields you changed - don’t approve or deny for now
61. Set ‘AutoApprovePatronProfileSettings’ in sysprefs to “Enable”
62. Reset your patron’s expiry date to today’s date
63. In the OPAC, click into Personal details and then back into Summary again (we need to do this to remove the success parameter from the URL)
64. The renewal link will be visible again - go through the process again
65. On the form, you should see an alert that you have already sent a modification request
66. This time, change a different field to the previous attempt
67. Submit the form
68. In the intranet there should be no modification this time and the change should be visible in the patron record as it has been automatically approved and handled as per the syspref
69. prove t/db_dependent/Koha/Patron.t
70. prove t/db_dependent/api/v1/patrons_self_renewal.t
71. yarn cypress run --spec t/cypress/integration/Islands/SelfRenewal.ts
72. Sign off!

Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:00 -06:00
Matt Blenkinsop 133946e50c
Bug 26355: Add a Vue island for the self-renewal modal
Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:30:00 -06:00
Matt Blenkinsop 4979f0aead
Bug 26355: Add the new category fields to the UI
Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:29:59 -06:00
Matt Blenkinsop 6ef6788406
Bug 26355: Update the API specs
Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:29:59 -06:00
Matt Blenkinsop a9fda16f38
Bug 26355: DBIC file
Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:29:58 -06:00
Matt Blenkinsop dfcb16a9ce
Bug 26355: Add new columns to the patron categories table
Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-05-01 09:29:52 -06:00
Lucas Gass f0436bd140
Bug 41882: (follow-up) Fix failing xt/tt_kohaTable_tidy.t test
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-30 14:06:22 -06:00
Lucas Gass 482c323144
Bug 41823: (follow-up) Remove redundant table_settings variable
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-30 14:06:22 -06:00
Lucas Gass 847fba0505
Bug 39516: (follow-up) Move table_settings variable
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-30 14:06:21 -06:00
Lucas Gass 3d02462f73
Bug 39516: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-30 14:06:06 -06:00
Owen Leonard 00886b1f75
Bug 42104: The term ID should be capitalized
This patch corrects several instances in the templates where "ID" is
incorrectly capitalized, "id" or "Id."

To test, an inspection of the patch/diff should be enough to confirm
that the changes are correct.

Signed-off-by: Andrew Auld <andrew.auld@openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:30 -06:00
Lucas Gass ad1d973380
Bug 34353: DBRev 25.12.00.055
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:29 -06:00
Martin Renvoize 7c25f2d4f1
Bug 34353: (QA follow-up) Fix QA script failures
- Replace IFNULL with COALESCE in the atomicupdate to satisfy the
  forbidden_patterns check (IFNULL is non-standard SQL).
- Run misc/devel/tidy.pl over the modified catalogue_detail.inc to
  satisfy the tidiness check.

Test plan:
1. Run the QA script against the patch series and confirm both
   forbidden_patterns and tidiness checks pass.
2. Re-run the parent commit's test plan and confirm spine-label column
   visibility behaves identically.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:29 -06:00
Martin Renvoize 1de09a6294
Bug 34353: (QA follow-up) Atomicupdate cleanup
- Set the executable bit on the atomicupdate so it passes the QA
  file_permissions check.
- Drop the unused 'say_info' import; only 'say_warning' and 'say_success'
  are now needed.
- Capture the row count from the DELETE so we report 'say_warning' on a
  re-run (when the syspref is already gone) instead of falsely claiming
  to have deleted it again.
- Add an inline comment explaining the conservative
  '(is_hidden OR VALUES(is_hidden))' conflict-resolution rule used when a
  table-settings row already exists alongside the old syspref value.

Test plan:
1. Verify the atomicupdate file has the executable bit set.
2. On a database where the syspref is still present, run the
   atomicupdate. Confirm output reads
   "Deleted 'SpineLabelShowPrintOnBibDetails' syspref".
3. Run the atomicupdate a second time. Confirm output now reads
   "'SpineLabelShowPrintOnBibDetails' syspref already removed".
4. Confirm the columns_settings rows are unchanged on re-run.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:28 -06:00
Martin Renvoize 8f6daa1b38
Bug 34353: (QA follow-up) Migrate spinelabel for both holdings tables
The catalogue_detail.inc include is shared by both the holdings_table
and otherholdings_table tabs, so the column visibility migration must
seed both. Without this, sites that previously had
'SpineLabelShowPrintOnBibDetails' set to "Don't show" would still see
the spine-label column appear in the other-holdings tab.

Also mark 'spinelabel' as is_hidden by default in admin/columns_settings.yml
for both tables, so fresh installs (where the atomicupdate doesn't run)
match the previous syspref default of 0 (Don't show).

Test plan:
1. Reset the database, do not apply the parent patch yet.
2. With the syspref still present, set it to "Don't show" and save.
3. Apply the parent patch and this follow-up, then update the database.
4. Navigate to a record with items in both 'Holdings' and 'Other holdings'
   tabs.
5. Verify the 'Spine label' column is hidden in BOTH tabs.
6. Reset the database again. Set the syspref to "Show", save, then apply
   patches and update the database.
7. Verify the 'Spine label' column is visible in BOTH tabs.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:28 -06:00
Olivia Reynolds 994feda588
Bug 34353: Remove 'SpineLabelShowPrintOnBibDetails' syspref
With the ability to customise which columns are hidden by default as
part of "Table settings", this syspref is redundant. As part of the
atomicupdate, we incorporate the previous syspref value into
columns_settings.

As there are six possible states the migration could start from (the
syspref can be 0 or 1, and the table setting can be 0, 1, or NULL) and
two final states it must be idempotent from, the test plan is
unfortunately quite cumbersome.

Test plan:
1.  Reset the database, to clear the table settings.
2.  Apply patch and update the database.
3.  Navigate to Administration > System preferences > Cataloging.
4.  Verify that "SpineLabelShowPrintOnBibDetails" does not appear.
5.  Navigate to a catalogue detail page.
6.  Verify that the "Spine label" column is hidden.
7.  Update the database again.
8.  Navigate to a catalogue detail page.
9.  Verify that the "Spine label" column is hidden.
10. Revert the patch.

11. Reset the database, to clear the table settings.
12. Navigate to Administration > System preferences > Cataloging.
13. Set "SpineLabelShowPrintOnBibDetails" to "Show" and save.
14. Apply patch and update the database.
15. Navigate to a catalogue detail page.
16. Verify that the "Spine label" column is visible.
17. Update the database again.
18. Navigate to a catalogue detail page.
19. Verify that the "Spine label" column is visible.
20. Revert the patch.

21. Reset the database, to clear the table settings.
22. Navigate to Administration > Table settings.
23. Under Catalog > holdings_table, check "Is hidden by default" for
    "spinelabel" and save.
24. Apply patch and update the database.
25. Navigate to a catalogue detail page.
26. Verify that the "Spine label" column is hidden.
27. Revert the patch.

28. Reset the database, to clear the table settings.
29. Navigate to Administration > System preferences > Cataloging.
30. Set "SpineLabelShowPrintOnBibDetails" to "Show" and save.
31. Navigate to Administration > Table settings.
32. Under Catalog > holdings_table, check "Is hidden by default" for
    "spinelabel" and save.
33. Apply patch and update the database.
34. Navigate to a catalogue detail page.
35. Verify that the "Spine label" column is hidden.
36. Revert the patch.

37. Reset the database, to clear the table settings.
38. Navigate to Administration > Table settings.
39. Under Catalog > holdings_table, uncheck "Is hidden by default" for
    "spinelabel" and save.
40. Apply patch and update the database.
41. Navigate to a catalogue detail page.
42. Verify that the "Spine label" column is hidden.
43. Revert the patch.

44. Reset the database, to clear the table settings.
45. Navigate to Administration > System preferences > Cataloging.
46. Set "SpineLabelShowPrintOnBibDetails" to "Show" and save.
47. Navigate to Administration > Table settings.
48. Under Catalog > holdings_table, uncheck "Is hidden by default" for
    "spinelabel" and save.
49. Apply patch and update the database.
50. Navigate to a catalogue detail page.
51. Verify that the "Spine label" column is visible.

Thanks-to: Katrin Fischer
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-04-29 09:09:27 -06:00
Lucas Gass 1fe357b289
Bug 42169: (follow-up) When changing to a new category set messaging prefs dirty
Signed-off-by: John Vinke <jvinke@austinseminary.edu>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:27 -06:00
Lucas Gass 0809d09222
Bug 42169: Unify patron category change popups
To test:
1. APPLY PATCH, restart_all
2. Go to  Administration > Patron categories
3. Edit or create some categories with different Enrollment periods and messaaging preferences
4. Find or create some patrons in different categories than the ones setup in step 3.
5. Edit the patron and change the category to one setup in step 3.
6. Notice a popup asking about both patron expiratoin date and messaging preferences, both should be checked by default.
7. Confirm and check that both the patron expiration date and messaging preferences updated to the category defaults.
8. Try again, checking only one of the 2 checkboxes. Make sure things updated correctly. Try this with both checkboxes.

Signed-off-by: John Vinke <jvinke@austinseminary.edu>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:26 -06:00
David Nind 1dd005c04d
Bug 42474: Patron categories form label - fix spelling for "Upperage limit"
Fix the spelling for a patron categories form label. "Upperage limit"
should be "Upper age limit" (there should be a space between upper and
age). Also fixes the table row label when attempting to delete a
patron category.

Test plan:
1. Go to Administration > Patrons and circulation > Patron categories.
2. Add a new patron category (or edit an existing category).
3. Notice that the label "Upperage limit" has no space between
   upper and age.
4. Attempt to delete a patron category, for example, Staff.
5. Note the itable row label is "Upperage limit".
6. Apply the patch.
7. Reload the pages for steps 2 and 4.
8. Note that the labels are now splet correctly as "Upper age limit".

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Gretchen Maxeiner <maxeinergl@gcc.edu>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:26 -06:00
David Nind dc40379b7d
Bug 42434: Fix capitalization for notice and slip configuration pages
Bug 35267 (Clarify CSS options for notices) added several new sections
to notice and slip configuration pages. However, the headings and
some sentences use capital case instead of sentence case.

This updates the text for the headings and sentences to use sentence
case. It also adds full stops to some alert sentences for
consistency.

Test plan:
1. Go to Tools > Patrons > Notices and slips.
2. Edit a notice or slip.
3. Note that several headings are capitalized, instead of using
   sentence case.
4. Expand each heading, and notice that some text doesn't use
   sentence case.
5. Apply the patch.
6. Refresh the notice or slip page.
7. Note that headings and text now use sentence case (and sentences
   in alerts now have a full stop).

Examples - headings in capital case:
- Global HTML Notice Styles
  . All Notice CSS
  . Email Notice CSS
  . Print Notice CSS
  . Print Slip CSS
...and further down the page under "Default language: English"
- Local HTML Notice Styles

Examples - text in expanded sections in capital case:
- CSS to be applied to email notices in addition to that already
  in "All Notice CSS"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:25 -06:00
David Nind 1357768b00
Bug 42140: Add a space between patron name and relationship
There is no space between the guarantor name and relationship on the
patron's details page in the staff interface, for example:
Henry Acevedo(father).

Test plan:
1. Go to Patrons.
2. Search for Lisa Charles (borrower number = 37).
3. Select Edit.
4. In the patron guarantor section, click on "Add guarantor":
   - Search for and select Henry as the guarantor
   - For relationship, select an option
5. Click Save.
6. Go to the details section.
7. Note that under the Guarantor heading there is no space between the
   guarantor's surname and the relationship,
   for example: Henry Acevedo(father)
8. Apply the patch.
9. Refresh the page.
10. Note that there is now a space between the surname and relationship.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:25 -06:00
Lucas Gass 845b933001
Bug 39516: DBRev 25.12.00.054
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:24 -06:00
Martin Renvoize 03397272c9
Bug 39516: (follow-up) Fix column drift when columns are hidden
The records-table row callback populated cells with positional
`$("td:eq(N)", nRow)` selectors. When a user hid a column (e.g. the
new "Score breakdown" column) via admin/columns_settings.yml, the
subsequent cells' positional indices shifted, causing the score
breakdown values to render into the Diff column.

Tag each rendered cell with `data-colname` via `createdCell` and
replace every `td:eq(N)` selector with a `td[data-colname='NAME']`
selector. Name-based selectors are unaffected when a column is
hidden: the selector simply matches zero elements and the append
is a no-op, while the remaining columns continue to receive the
correct content.

This aligns with the `bKohaColumnsUseNames` convention already
used by datatables.js (`_dt_visibility`, `_dt_force_visibility`,
the colvis button).

Test plan:
1. Stage a MARC file whose records produce matches (populating
   composite_scores).
2. Confirm Match details, Score breakdown and Diff cells render
   correctly with the default column layout.
3. Go to Administration > Table settings > tools >
   manage-marc-import > records-table and hide the "Score
   breakdown" column. Reload the staged batch view.
4. Verify the Score breakdown column is gone, the Diff column
   still shows "View" links, and the Record column still shows
   View/Edit buttons.
5. Repeat for other toggleable columns (Match details, Diff,
   Record) and confirm no drift.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Jeanne Mauriello <jeanne.mauriello@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:24 -06:00
Martin Renvoize 1d8a60083c
Bug 39516: (follow-up) Add missing POD for private subroutines
Adds minimal POD stubs for all private (underscore-prefixed) subroutines
in C4/Matcher.pm and C4/ImportBatch.pm to satisfy the QA script's
pod_coverage check.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Jeanne Mauriello <jeanne.mauriello@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:23 -06:00
Martin Renvoize adc1651a52
Bug 39516: Display composite match scores in staged batch view
Adds a new "Score breakdown" column to the staged MARC batch view that
shows the per-matchpoint score contributions for each candidate match,
e.g. "title:50, author:50" or "isbn:100", rather than a single opaque
total.

The column is hidden by default and can be toggled via the standard
column configuration tool (Administration > Table settings > Tools >
manage-marc-import). The existing "Match details" column continues to
show the numeric total score unchanged.

Falls back to displaying the raw aggregate score for any pre-existing
import_record_matches rows that have no composite_scores data.

To test:
1. Ensure you have a bibliographic matching rule with at least two
   matchpoints, e.g.:
     - Match point: search index "title",  score 50, tag 245$a
     - Match point: search index "author", score 50, tag 100$a
     - Match point: search index "isbn",   score 100, tag 020$a
   Set the threshold to 100.
2. Prepare two MARC files:
   a. A record that matches an existing catalogue entry via title + author
      (but not ISBN).
   b. A record that matches solely via ISBN.
3. Stage each file via Tools > Stage MARC records for import, applying
   the matching rule above.
4. Open the staged batch view for each batch.
5. Confirm the "Match details" column still shows the total numeric score
   as before, e.g. "(score=100)".
6. Use the column visibility toggle (or Administration > Table settings >
   Tools > manage-marc-import) to show the "Score breakdown" column.
7. Verify the breakdown column shows:
   a. File (a): "title:50, author:50"
   b. File (b): "isbn:100"
8. Confirm the "Score breakdown" column is hidden by default and the
   column toggle persists across page reloads.
9. Verify that pre-existing staged batches (run before this patch) still
   display gracefully — the breakdown column falls back to the numeric
   score.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Jeanne Mauriello <jeanne.mauriello@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:23 -06:00
Martin Renvoize 062e48ed8f
Bug 39516: Record per-matchpoint score breakdown when staging MARC records
Extends C4::Matcher::get_matches() to track each matchpoint's individual
score contribution alongside the aggregate total. A new `composite_scores`
hashref (keyed by search_index) is included in every result entry.

Updates C4::ImportBatch::SetImportRecordMatches() to JSON-encode and
persist the composite scores in the new column, and
GetImportRecordMatches() to decode and return them.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Jeanne Mauriello <jeanne.mauriello@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:22 -06:00
Martin Renvoize 382b06dd28
Bug 39516: Add composite_scores column to import_record_matches
Adds a new nullable longtext column `composite_scores` to the
`import_record_matches` table to store a JSON object mapping each
matchpoint search_index to the score it contributed to the total.

This allows the UI to display a breakdown of match scores (e.g.
"title:50, author:50") rather than just the aggregate total.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Jeanne Mauriello <jeanne.mauriello@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:22 -06:00
Lewis Clay d34e09d2f6
Bug 42131: Terminology: Return in action logs should be Check-in
The problem that arises here is that return meaning
'return to previous page' as a verb and 'Return' as a substantive
require different translation, at least in German.

Updated the display text 'Return' to 'Check-in' in:
- action-logs.inc: The action log display for the RETURN case

This resolves problems in the translation

Test plan:
1. Navigate to a page displaying Action logs
2. Look for entries with the RETURN action
3. Confirm the display text now shows 'Check-in' instead of 'Return'

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:21 -06:00
Andrew Fuerste Henry 7e34351ede
Bug 41882: (QA follow-up): Correct table settings values
The prior patch incorrectly linked to 'circ' and 'holds' in the table settings, causing the Configure to take one to the wrong part of the table settings page and saved table settings to not be enforced.

To test:
1: Apply all patches, restart_all
2: Attempt a batch modification of two or more holds such that at least one modification will succeed and at least one will fail
3: On the results page, confirm the Configure button on both the "Updated holds" and "Failed holds" tables correctly lead to the modified_holds table settings
4: Adjust settings for the modified_holds table
5: Confirm your changes are reflected in the results page for subsequent batch hold modifications

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:21 -06:00
Lucas Gass cc3b7cebf5
Bug 41882: Enforce pickup location rules
To test:
1- Set ReservesControlBranch to 'item's home library'
2- Set Main library's 'Holds and bookings policies by item type' so that PASS items can only be picked up at Main
3- Place a hold on a bib whose only item is a PASS belonging to Main
4- Use the Batch hold modification tool to identify the hold you just placed
5- Update the pickup location to North. Note that the update is successful, and there's no indication that the pickup location is now a problem.
6- Check in Main's PASS, and note that the hold doesn't trigger to fill
7- APPLY PATCH, restart_all
8- Try again, this time the hold should fail to update at all.
9- You should see a new table with the 'Failed hold' in it, it should also include a 'Result' column which explains why the hold failed (New pickup location is invalid).
10- Try modifying a hold to an invalid pickup location, but also attempt to modify something else on the hold at the same time, like expiration date.
11- The hold should not modify anything. If the pickup location is invalid all other modifications should NOT happen on that hold.
12 - Play with the Table settings for the holds_modified tables, ensuring you can hide/show all the columns.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:20 -06:00
David Cook 63f8c65b36
Bug 42463: Use text instead of HTML for confirm message
This change uses the text instad of the HTML for the delete SMS
provider confirm message.

Test plan:
0. Apply the patch
1. Go to Administration, click System preferences, search for SMSSendDriver
2. Set SMSSendDriver to "Email"
3. Go to administration
4. Click on "SMS cellular providers"
5. Click "New SMS provider"
6. For "Name" and "Domain" write: This & That
7. Click Submit
8. Click "Delete" for the new provider
9. Note that the message says "Are you sure you want to delete This & That?"
10. Click "OK"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:20 -06:00
Kevin Carnes 7e23ab9f93
Bug 42177: Allow managing bundles
There is no preference for bundlesEnabled, so use the variable instead.

Test plan:
1. Edit a record.
2. Modify the leader so that it has a collection bibliographic level.
3. Ensure that is has at least one item.
4. Observe that clicking the manage bundle button next to the item
   has no effect.
5. Apply the patch.
6. Observe that the manage bundle button now works.
7. Sign off

Sponsored-by: Lund University Library
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:19 -06:00
Owen Leonard 0ebcc2b7fc
Bug 41827: (follow-up) Correct input label and id
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:19 -06:00
Owen Leonard 2f71ea8407
Bug 41827: Update authority types pages to use grid layout for forms
This patch updates authority types administration templates with
new markup and CSS for layout forms using CSS grids.

To test, apply the patch rebuild the staff interface CSS.

Check the following pages, confirming that the "New" and "Edit" versions
of the form look correct in each case:

- Administration -> Authority types
- Administration -> Authority types -> Tags
- Administration -> Authority types -> Subfields

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:18 -06:00
Owen Leonard cd73ef9f34
Bug 42267: Update lists pages to use grid layout for forms
This patch updates lists templates with new markup for laying out forms
using CSS grids.

To test, apply the patch rebuild the staff interface CSS.

Check the following pages, confirming that the forms look
correct.

- Lists -> New list
- Lists -> Edit list
- Lists -> View list -> Add records
- Lists -> View list -> Send list
- Lists -> View list -> Download
  - We don't directly access the template for downloading lists anymore,
    but it's still there:
    http://127.0.0.1:8081/cgi-bin/koha/virtualshelves/downloadshelf.pl?shelfnumber=1

Sponsored-by: Athens County Public Libraries
Signed-off-by: Gretchen Maxeiner <maxeinergl@gcc.edu>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:18 -06:00
Owen Leonard cac0556c53
Bug 41823: Update acquisitions admin pages to use grid layout for forms
This patch updates acquisitions-related administration templates with
new markup and CSS for layout forms using CSS grids.

To test, apply the patch rebuild the staff interface CSS.

Check the following pages, confirming that the "New" and
"Edit" versions of the form look correct in each case:

- Administration -> Currencies and exchange rates
- Administration -> Budgets (New, Edit, and Duplicate)
- Administration -> Contracts
- Administration -> Funds -> Add sub fund to a budget
- Administration -> EDI accounts
- Administration -> Library EANs
- Acquisitions -> Vendor -> Contracts

Sponsored-by: Athens County Public Libraries
Signed-off-by: Jessie Z <jzairo@bywatersolutions.com>

Bug 41823: (follow-up) Missed a form requiring an upgrade

- Actions -> Close

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:17 -06:00
Owen Leonard 09c5bd5a9a
Bug 40113: Update accounting admin pages to use grid layout for forms
This patch updates four administration templates to use new structure
and CSS for layout forms using CSS grids.

To test, apply the patch and go to Administration. Check the following
pages, confirming that the "New" and "Edit" versions of the form look
correct in each case:

- Administration -> Debit types
- Administration -> Credit types
- Administration -> Cash registers
- Administration -> Desks

Sponsored-by: Athens County Public Libraries
Signed-off-by: noah <noah@inlibro.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:17 -06:00
Owen Leonard 2c7e644cee
Bug 39780: (follow-up) Don't use wide layout for modal
Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:16 -06:00
Owen Leonard 2566064028
Bug 39780: (follow-up) Adjust layout of checkbox inputs at larger widths
Also adjusting the markup on the library add/edit form so that yes/no
selects are narrower.

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:16 -06:00
Owen Leonard e97636c6df
Bug 39780: Update library groups form to use grid layout
This patch updates the library groups page to convert forms to use the
grid layout introduced in Bug 39483. Some changes are made the global
form-grid CSS:

- Use container queries rather than media queries to control the
  responsive behavior of forms using the grid
- Add ".input-md" and ".input-sm" classes to be used when a form's
  <input> corresponds to a database column which is smaller. In this
  case:
    ".input-sm" for the row containing branches.branchcode (varchar 10)
    ".input-md" for the row containing branches.branchzip (varchar 25)
  - These classes have been added to branches.tt

The library groups template has been updated with the new form grid
markup.

To test, apply the patch and rebuild the staff interface CSS.

- Go to Administration -> Libraries -> New library
  - In the entry form, most form fields should equally wide.
  - The library zipcode, IP, and MARC organization code fields should be
    narrower.
  - The library code field should be narrowest.
- Go to Administration -> Library groups -> New group. The "Add group"
  form should be laid out vertically.
  - Also test the form shown when editing a group.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:15 -06:00
Martin Renvoize dc86d450d8
Bug 34025: (QA follow-up) Restore 730 ind2!=2 filter for Uniform titles
The previous patch incorrectly changed the 730 indicator filter from
[@ind2!=2] to [@ind2=2], which inverts which 730 fields display.
Most 730 fields have a blank second indicator (no information provided),
so this change caused them to disappear from the Uniform titles section.

This restores the original filter so 730 fields with blank or
non-analytical indicators continue to display as Uniform titles.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:15 -06:00
Martin Renvoize 3c72e5d9c5
Bug 34025: (QA follow-up) Fix subfield $t handling for 7XX contained works
When 7XX fields contain both name ($a) and title ($t) subfields,
the link should search for the title rather than the name.
This is important for contained works that reference titles
within larger works.

This patch updates both intranet and OPAC XSLT files to check
for the presence of subfield $t and use title search (ti,phr:)
instead of author search (au:) when $t is present.

Test plan:
1. Find or create a bibliographic record with 7XX fields containing both $a (name) and $t (title) subfields
   - For example: 700 $a Smith, John $t The Great Adventure
2. View the record detail page in both staff interface and OPAC
3. In the "Contained works" section, click on the link for the 7XX field entry
4. Verify the search is performed as a title search (ti,phr:"The Great Adventure") rather than author search (au:"Smith, John")
5. Confirm the search results show records matching the title from subfield $t
6. Test with records that have 7XX fields with only $a subfields to ensure they still search by author
7. Repeat testing for different 7XX field types (700, 710, 711)

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:14 -06:00
Bernard Scaife 1cc432c168
Bug 34025: Changing default link search from au to ti,phr for X30 fields as they are are title type ones. These apply if there is no authorised term linked to for the content
Test plan:
==========
1. Add some 130, 240, 730 + 7XX $a + $t fields to a record. Note that 730
   and 7XX $a + $t fields have to have 2nd indicator set to 2.
   You can link the fields to authority records. Ensure that one of the fields is not linkable to an authority record and leave it unlinked.
2. Note that the information displayed from these fields is not
   'clickable' neither in OPAC, nor in the staff interface.
3. Apply the patch ; restart all.
4. You should be able to see and use the links now. If the fields are
   linked to authotiry record and UseAuthoritiesForTracings
   systempreference is set, the search will be performed with
   authid identifier. There should be also a magnifying glass icon
   with a link to the authority record.
5. The links for any unlinked authority should now point to ti,phr - eg /cgi-bin/koha/catalogue/search.pl?q=ti,phr:%22Uniform%20Bernard%22 instead of au
6. Click that link and ensure it finds at least one record (the one you started with)

Signed-off-by: Bernard Scaife <bernard.scaife@openfifth.co.uk>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:14 -06:00
Janusz Kaczmarek 129c750218
Bug 34025: Make more controlled fields linkable (130, 240, 730 + 7XX $a + $t) [XSLT]
MARC21 contain linking fields (130, 240, 730 + 7XX $a + $t) that currently
currently are not 'clickable'. This patch adds the links, similarly to the links
created for contributors', both in OPAC and staff interface.

Test plan:
==========
1. Add some 130, 240, 730 + 7XX $a + $t fields to a record. Note that 730
   and 7XX $a + $t fields have to have 2nd indicator set to 2.
   You can link the fields to authority records.
2. Not that the information displayed from these fields is not
   'clickable' neither in OPAC, nor in the staff interface.
3. Apply the patch ; restart all.
4. You should be able to see and use the links now. If the fields are
   linked to authotiry record and UseAuthoritiesForTracings
   systempreference is set, the search will be performed with
   authid identifier. There should be also a magnifying glass icon
   with a link to the authority record.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Bernard Scaife <bernard.scaife@openfifth.co.uk>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:13 -06:00
Martin Renvoize 67df3e1e85
Bug 41901: (follow-up) Fix perlcritic violation in unit tests
Remove explicit "return undef" in favour of bare "return" to
satisfy Perl::Critic (PBP p.199).

Signed-off-by: Brian J. Barr <brian.barr@churchofjesuschrist.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:13 -06:00
Martin Renvoize cb8f1ff9d5
Bug 41901: Allow duplicate check when adding authority via API
POST /api/v1/authorities currently offers only the heading-based
C4::AuthoritiesMarc::FindDuplicateAuthority check, and the only
escape hatch is the `x-koha-override` header.

Koha already ships configurable record matching rules (see
Administration > Record matching rules) and C4::Matcher already
supports `record_type = authority`. This patch lets an API caller
opt into one of those rules when creating an authority by sending
its numeric id in a new `x-koha-matchrule` header.

Behaviour:
* If no `x-koha-matchrule` header is sent the existing heading-based
  duplicate check is used. Existing clients are unaffected.
* If `x-koha-matchrule: <id>` is sent:
    * non-existent id          -> 400 `matching_rule_not_found`
    * matcher not record_type=authority
                               -> 400 `invalid_matching_rule_type`
    * matcher returns matches  -> 409 `duplicate`; the response
                                  body additionally contains a
                                  `matches: [{authid, score}, ...]`
                                  array listing every hit returned
                                  by the matcher
    * matcher returns nothing  -> record is created (201)
* `x-koha-override: any|duplicate` continues to bypass all duplicate
  checks, including the matcher.

A non-integer header value is rejected by the OpenAPI schema
validator before the controller runs.

Test plan:
 1) Apply the patch series.
 2) Run `prove t/db_dependent/api/v1/authorities.t`
    => all subtests pass.
 3) Log in to the staff interface as a user with the
    'manage_matching_rules' subpermission and go to
    Administration > Record matching rules > New record matching
    rule:
     a) Set Matching rule code e.g. `AUTH_HEADING`
     b) Set Description e.g. `Authority heading match`
     c) Set Record type to `authority`
     d) Set Threshold to e.g. `1000`
     e) Add a match point:
         * Search index:  `Heading-Main`  (or your authorities index)
         * Score:         `1000`
         * Tag:           `110` (or the heading tag used by your
                          authority type)
         * Subfields:     `a`
     f) Save. Note the numeric matching rule id in the edit URL
        (`admin/matching-rules.pl?op=edit_matching_rule&matcher_id=N`).
 4) Export an existing authority record as MARCXML (for example
    from the authority details page). Save it as `duplicate.xml`.
 5) From the command line, POST the record back to Koha using the
    new header (replace <N>, credentials and host):

    curl -u <user>:<pass> -X POST \
      -H 'Content-Type: application/marcxml+xml' \
      -H 'x-authority-type: CORPO_NAME' \
      -H 'x-koha-matchrule: <N>' \
      --data-binary @duplicate.xml \
      http://<your-koha>/api/v1/authorities

    => Expect HTTP 409. The JSON response body contains
       `error_code: "duplicate"` and a `matches` array listing the
       authid(s) that were found along with their score.
 6) Edit `duplicate.xml` so the main heading is something that is
    not already in the catalogue. POST it again with the same
    header.
    => Expect HTTP 201 and a `Location` header pointing to the new
       authority.
 7) Re-run the POST from step 5 but additionally send
    `-H 'x-koha-override: duplicate'`.
    => Expect HTTP 201 (override bypasses the matcher).
 8) Send a POST with `x-koha-matchrule: 999999`.
    => Expect HTTP 400 with `error_code: "matching_rule_not_found"`.
 9) Create a second matching rule with Record type `biblio` and
    note its id. POST with `x-koha-matchrule` set to that id.
    => Expect HTTP 400 with
       `error_code: "invalid_matching_rule_type"`.
10) POST with `x-koha-matchrule: abc`.
    => Expect HTTP 400 from the OpenAPI schema validator.
11) POST without `x-koha-matchrule`.
    => Expect the existing heading-based duplicate check to run
       exactly as before (409 for a known duplicate, 201 otherwise).
12) Sign off.

Signed-off-by: Brian J. Barr <brian.barr@churchofjesuschrist.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:12 -06:00
Martin Renvoize 5feb1e03eb
Bug 41901: Unit tests
Extends the existing `post() tests` subtest in
t/db_dependent/api/v1/authorities.t with coverage for the new
`x-koha-matchrule` header that selects a Koha record matching rule
(`C4::Matcher`, record_type=authority) for duplicate detection when
creating authorities via POST /api/v1/authorities.

The new cases exercise:
* non-integer id (rejected by the OpenAPI schema validator)
* unknown matcher id -> 400 `matching_rule_not_found`
* biblio matcher id -> 400 `invalid_matching_rule_type`
* valid authority matcher returning duplicates -> 409 with a
  `matches` array of `{authid, score}` objects
* valid authority matcher returning no duplicates -> 201
* matcher combined with `x-koha-override: duplicate` -> 201
  (override wins, matcher is skipped)

`C4::Matcher` is mocked deterministically to avoid relying on the
authority search engine in the test database.

These tests fail without the controller/spec changes introduced in
the follow-up commit.

Test plan:
1) Apply only this patch and run
   `prove t/db_dependent/api/v1/authorities.t`.
   => Failures are expected in the `post() tests` subtest for the
      `x-koha-matchrule` cases.
2) Apply the next patch in the series and re-run the tests.
   => All subtests pass.

Signed-off-by: Brian J. Barr <brian.barr@churchofjesuschrist.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:12 -06:00
Martin Renvoize d463fce02d
Bug 37966: Fix renewal on hold override in circulation.pl
This extends the fix from Bug 37966 to circ/circulation.pl. The original
bug fix addressed circ/renew.pl, where renewing a book on hold with an
empty due date would result in the due date being set to "now" (today).

The same issue occurs in circ/circulation.pl when:
1. Attempting to check out an item that is already checked out to the
   same patron (which triggers a renewal)
2. The item has a hold on it, triggering the hold override dialog
3. The librarian overrides the hold without specifying a due date

In this scenario, the renewal would incorrectly set the due date to
today instead of calculating it based on circulation rules.

CHANGES:

1. circ/circulation.pl (lines 550-558)
   - Added logic to handle the `renewonholdduedate` parameter when
     both RENEW_ISSUE and RESERVED conditions are present
   - Follows the same pattern as circ/renew.pl
   - If `renewonholdduedate` is empty, passes empty string to AddIssue/
     AddRenewal, allowing proper date calculation

2. koha-tmpl/.../circulation.tt (lines 351-356)
   - Added "Renewal due date" input field in the hold override dialog
   - Only appears when both RESERVED and RENEW_ISSUE are true
   - Optional field - if left empty, due date is calculated per
     circulation rules

TEST PLAN:

1. Enable the RenewalOnHoldOverride system preference (set to "Allow")

2. Set up a patron, item, and circulation rules:
   - Create a patron (or use existing)
   - Create an item (or use existing)
   - Ensure circulation rules have a renewal period (e.g., 14 days)

3. Create a hold scenario:
   - Check out the item to the patron
   - Place a title-level hold on the same item (you may need a second
     patron or use the same patron depending on your holds rules)

4. Test renewal via circulation.pl:
   - Go to Circulation (Home > Circulation)
   - Scan the patron's barcode
   - Scan the item's barcode (the one that's already checked out with
     a hold)
   - Observe the hold override dialog appears

5. Test WITHOUT this patch:
   - Leave the "Renewal due date" field empty
   - Click "Yes, renew"
   - BUG: The due date is set to today (same as check-in date)

6. Test WITH this patch:
   - Leave the "Renewal due date" field empty
   - Click "Yes, renew"
   - SUCCESS: The due date is calculated based on circulation rules
     (e.g., 14 days from now if that's your renewal period)

7. Test optional due date override:
   - Repeat the renewal process
   - This time, enter a specific date in "Renewal due date" field
   - Click "Yes, renew"
   - SUCCESS: The due date is set to your specified date

8. Test SpecifyDueDate preference interaction:
   - If SpecifyDueDate is enabled and a due date is set via that
     interface, it should take precedence over renewonholdduedate

TECHNICAL NOTES:

The fix ensures that when `renewonholdduedate` is empty (empty string),
it's passed through to AddIssue which then calls AddRenewal. The
improved empty string handling in C4::Circulation (from the follow-up
commit) ensures empty strings are not parsed as "now" but instead
trigger proper date calculation based on circulation rules.

This matches the behavior fixed in circ/renew.pl and provides a
consistent user experience across both circulation interfaces.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:11 -06:00
Martin Renvoize 14de13d3ad
Bug 37966: (follow-up) Make empty string handling more explicit in Circulation.pm
This commit improves upon the original fix by making the intent more
explicit when handling empty string date parameters in AddRenewal and
AddIssue functions.

WHY THIS IS BETTER:

The original fix (changing `defined $datedue` to just `$datedue`)
works correctly but relies on Perl's implicit truthy/falsy semantics:

  if ( $datedue && ref $datedue ne 'DateTime' )

This is subtle because:
- Empty string "" is falsy, so parsing is skipped ✓
- But it's not immediately obvious that empty strings are being
  handled specially
- The code doesn't explicitly show its intent

THIS APPROACH:

  if ( defined $datedue && $datedue ne '' && ref $datedue ne 'DateTime' )

Benefits:
1. EXPLICIT: Clearly states "only parse if defined AND not empty
   AND not already a DateTime"
2. SELF-DOCUMENTING: Intent is obvious to future developers
3. NO RELIANCE on truthy/falsy semantics
4. CONSISTENT: Same pattern applied across all date parameter handling

COMPREHENSIVE CHANGES:

1. AddRenewal (C4/Circulation.pm:3449)
   - Made datedue empty string check explicit

2. AddIssue (C4/Circulation.pm:1611)
   - Made datedue empty string check explicit
   - Also updated issuedate handling (line 1616) to explicitly handle
     empty strings

3. Test Coverage (t/db_dependent/Circulation/issue.t)
   - Added test for AddIssue with empty string datedue
   - Added test for AddIssue with empty string issuedate
   - Ensures both functions properly calculate dates from circulation
     rules instead of using "now"

The existing test for AddRenewal already covered empty string datedue
(added in the original Bug 37966 commit). These new tests ensure the
same correct behavior for AddIssue.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:10 -06:00
Michael Hafen 7dbaa5d77c
Bug 37966: Add test coverage for AddRenewal with empty due date
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:10 -06:00
Michael Hafen c84de245e3
Bug 37966: When Renewing a book on hold, don't require the due date
When renewing a book on hold with the RenewalOnHoldOverride sys pref set
to "Allow", if the due date is not specified then it becomes "now".  By
checking the input is set rather than just defined the due date is set as
expected based on circulation rules.
Also change the description of the RenewalOnHoldOverride sys pref as the
due date would no longer be required to be entered with this change.

Test plan:
1. make sure RenewalOnHoldOverride is set to 'Allow'
2. check out a book, then place a title-level hold on it
3. renew the book, see the hold override dialog
4. leave the due date empty and hit the override button
5. observe the new due date is "now"
6. apply patch and restart plack
7. renew the book again, overriding the hold, and see the expected due date
   as per circulation rules.

Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:09 -06:00
Lucas Gass cc0c3b3230
Bug 37671: DBRev 25.12.00.053
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:09 -06:00
Andrew Fuerste Henry 4586a37b7a
Bug 37671: (QA follow-up) Add exec flag to atomicupdate
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:08 -06:00
Andrew Fuerste Henry 3598cea2ba
Bug 37671: (QA follow-up) Tidy printreceipt.pl
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:08 -06:00
Martin Renvoize 528f81b7fd
Bug 37671: (follow-up) Use PAYOUT notice for payout receipts on patron account page
This patch adds support for printing PAYOUT receipts from the patron
account page (members/boraccount.pl) using the new PAYOUT notice template.

Previously, PAYOUT debits used the generic invoice print template. Now
they use the same dedicated PAYOUT receipt template that is used in the
POS register, providing consistent receipt formatting across both
interfaces.

Test plan:
1. Create a charge on a patron account (e.g., Lost item)
2. Pay the charge
3. Refund the payment with a cash payout
4. On the patron account page, click "Print" for the PAYOUT transaction
5. Verify the receipt uses the PAYOUT notice template showing:
   - Transaction details (ID, operator, payout type)
   - Refund details showing what was refunded
   - Total payout amount
6. Verify it matches the format used in the POS register

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:07 -06:00
Martin Renvoize e92dfc5ab0
Bug 37671: Fix POS receipt printing for refunds
The receipt printing logic was failing because it always expected a credit
but the "Print receipt" button was passing the payout debit line ID.

This fix:
- Updates pos/printreceipt.pl to use previously added PAYOUT template based
  on account line type
- Uses RECEIPT template for credits, PAYOUT template for PAYOUT debits
- Throws proper error for unsupported transaction types

Test plan:
1. Make a purchase through Point of Sale
2. Go to transaction history for the register used
3. Issue a refund for the purchase
4. Click "Print receipt" button for the refund
5. Verify receipt prints correctly with payout details

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:07 -06:00
Martin Renvoize 02fb697d21
Bug 37671: Add new 'PAYOUT' notice template
When a refund is processed through Point of Sale, the system creates:
1. A credit account line (the refund)
2. A debit account line (the payout)

The receipt printing logic currently only accounts for RECEIPTs against
credits.

This patch adds a new 'PAYOUT' template for dealing with the negative
debits that make up a payout so we can construct a proper receipt for
this action.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:06 -06:00
Jonathan Druart 9499e739f0
Bug 42185: Remove C4::Context->new_dbh
This is not used!

Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:09:06 -06:00
Jonathan Druart d533f2bbff
Bug 42185: Remove C4::Context::_new_dbh
This looks trivial at first, but it's not, because of forking and the
comment in C4::Context
I don't think the comment makes sense, we need to retrieve a new dbh,
and so passing the 'new' parameters to C4::Context->dbh should do the
same.

Test plan:
Try rebuild_zebra.pl and confirm there is no error
 % perl misc/migration_tools/rebuild_zebra.pl --processes 2 -b

Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-29 09:08:59 -06:00
Jonathan Druart 224ba4ab48
Bug 42252: Prevent XSS when deleting a list at the OPAC
Test plan:
Create a list with name=<script>alert("booh!");</script>
Delete it at the OPAC

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:25 -06:00
Marcel de Rooy f4e21f4199
Bug 42254: (QA follow-up) Remove unused var, fixing a FIXME
Test plan:
Read the patch. Look for the var in the script.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:24 -06:00
David Cook 517edc11a8
Bug 42254: (follow-up) tweaks to improve XSS fix
This change removes useless success_* calls, removes an unnecessary
'use URI::Escape qw( uri_unescape );' in the Perl, adds a missing
response function argument, uses a safe sink for the "verdict" element
to make the content setting a bit cleaner.

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:24 -06:00
Jonathan Druart 822cd31930
Bug 42254: Prevent XSS when testing tags
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:23 -06:00
Lucas Gass 5d42ea5e96
Bug 34000: DBRev 25.12.00.052
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:23 -06:00
Martin Renvoize 0baaeb062e
Bug 34000: Unit tests
Adds a new subtest to t/db_dependent/Koha/Patron.t covering
fixup_cardnumber and the autoMemberNumValue counter:

- Normal increment bumps the counter
- Renaming a patron's cardnumber does not free it for reuse
- Deleting a patron does not free their cardnumber for reuse
- Counter wins when ahead of db_max
- db_max wins when ahead of counter
- Legacy fallback when the syspref row is absent

Also adds autoMemberNumValue to the exceptions list in
t/db_dependent/check_sysprefs.t. The preference is managed internally
by Koha::Patron::fixup_cardnumber -- it is not user-configurable and
has no entry in the admin preference YAML files.

Test plan:
1. prove t/db_dependent/Koha/Patron.t
2. prove t/db_dependent/check_sysprefs.t
3. prove t/db_dependent/Koha/Plugins/Barcode_transform_hooks.t
   (confirm the patron_barcode_transform hook is still called twice
   during store when autoMemberNum is enabled).

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:22 -06:00
Martin Renvoize 61f0432e9d
Bug 34000: Prevent re-issuance of auto-generated cardnumbers
When autoMemberNum is enabled, Koha picks the next cardnumber as
MAX(numeric cardnumber in borrowers) + 1. If patron A's auto-generated
cardnumber is later changed (e.g. they get a physical card) or the
patron is deleted, the original number becomes "free" and the next
self-registrant is assigned it -- inheriting anything third-party
services (OverDrive, Hoopla, unauthenticated self-check) have keyed on
the cardnumber.

This patch makes Koha::Patron::fixup_cardnumber use a monotonic counter
(the autoMemberNumValue syspref) so that once a number has been
auto-generated for any patron, it is never auto-generated again.

The syspref row is locked with SELECT ... FOR UPDATE inside the
existing txn_do, which serialises concurrent callers via InnoDB
row-level locking and addresses the race condition raised against
earlier revisions of this patch.

The formula is: next = MAX(counter, db_max) + 1. When the counter is 0
(pre-upgrade or missing syspref), this degrades gracefully to
db_max + 1 -- the legacy behaviour -- so no feature toggle is needed.
The counter is only bumped when Koha actually auto-generates a
cardnumber; plugin- or caller-supplied values do not touch it.

Test plan:
1. Enable autoMemberNum.
2. Self-register patron A via OPAC, note cardnumber N.
3. Edit patron A in the staff interface, change cardnumber to
   something non-numeric like "MANUAL-XYZ".
4. Self-register patron B.
5. Confirm B gets N+1, NOT N.
6. Delete patron A.
7. Self-register patron C.
8. Confirm C gets N+2, NOT A's original number.
9. Install a plugin implementing the patron_barcode_transform hook;
   confirm it still runs and its value is honoured (counter is not
   bumped when the plugin supplies a cardnumber).

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:22 -06:00
Martin Renvoize 43f0998586
Bug 34000: Add autoMemberNumValue system preference
Introduces a new system preference, autoMemberNumValue, which tracks
the highest auto-generated cardnumber ever issued. It is seeded from
the MAX cardnumber across both borrowers and deletedborrowers so
pre-upgrade deletions do not free numbers for re-issuance.

This preference is managed internally by Koha::Patron::fixup_cardnumber
and has no UI (no YAML entry).

Test plan:
1. Apply the atomicupdate:
   perl installer/data/mysql/updatedatabase.pl
2. Verify the preference exists:
   SELECT * FROM systempreferences WHERE variable = 'autoMemberNumValue';
3. Confirm the seed value matches:
   SELECT GREATEST(
       COALESCE((SELECT MAX(CAST(cardnumber AS SIGNED)) FROM borrowers
                 WHERE cardnumber REGEXP '^-?[0-9]+\$'), 0),
       COALESCE((SELECT MAX(CAST(cardnumber AS SIGNED)) FROM deletedborrowers
                 WHERE cardnumber REGEXP '^-?[0-9]+\$'), 0)
   );
4. Re-run the atomicupdate and confirm the already-exists warning.
5. Drop the syspref, re-apply the atomicupdate, confirm it seeds
   correctly on fresh installs via mandatory/sysprefs.sql.

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:21 -06:00
Jonathan Druart 9cac08f749
Bug 42136: Add tests
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:21 -06:00
David Cook 268863392e
Bug 42136: prevent cgi, directory, and file plugins from use in user-entered templates
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:20 -06:00
David Cook a2a92356b8
Bug 42253: (QA follow-up) prevent XSS injection in data- attribute
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:20 -06:00
Jonathan Druart e9787306cc
Bug 42253: Prevent XSS in the advanced editor - macros
Test plan:
/cgi-bin/koha/cataloguing/editor.pl
Click "Macros", "New macro..."
Enter <script>alert("booh");</script>
OK
Click on the macro name, run macro, delete macro, mark as public:
Nothing should trigger an alert

QA:
-            $( '#macro-list li[data-name="' + name + '"][data-id="' + id + '"]' ).addClass( 'active' );
+            $( '#macro-list li[data-id="' + id + '"]' ).addClass( 'active' );
Why did we need name and id to select the macro?

Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:19 -06:00
David Cook 41d84f621d
Bug 42366: Fix debug_mode test for koha-conf.xml
This patch fixes the debug_mode test for koha-conf.xml,
so that <debug_mode>0</debug_mode> works properly.

Test plan:
0. Do not apply the patch
1. sudo vi /etc/koha/sites/kohadev/koha-conf.xml
2. Change <debug_mode>1</debug_mode> to
<debug_mode>0</debug_mode>
3. sudo koha-plack --restart kohadev
4. ps -efww | grep "starman"
5. Note that starman is still in development mode

6. Apply the patch
7. sudo koha-plack --restart kohadev
8. ps -efww | grep "starman"
9. Note that starman is now in deployment mode

Note: this is important because running a production
system in debug/development mode can be a security risk!

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-28 15:48:12 -06:00
Jonathan Druart dbbc1aa53e
Bug 41566: Restore item groups ft
This code was outside of document.ready before the previous changes.

This patch fixes the whole item groups feature.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:25 -06:00
Kyle Hall 75005dca37
Bug 41311: (QA follow-up) Fix broken unit test
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:25 -06:00
Lucas Gass c91ae51718
Bug 42447: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:24 -06:00
Lucas Gass 693389c942
Bug 42447: DBRev 25.12.00.051
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:24 -06:00
Kyle M Hall f0a4659e3c
Bug 42447: Add unit tests
PA amended: Removed bug number from subtest

Signed-off-by: Charlie Arthur <carthur@slolibrary.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:23 -06:00
Kyle M Hall 7693360a0f
Bug 42447: SIP template fields in the database are too small
The SIP fields in the database are too small for the templates of some configurations, and are truncated during upgrade resulting in invalid templates. It's likely we need more than 255 characters for some templates. We should convert all sip template fields to TEXT fields.

Test Plan:
1) For each template field on a sip account attempt to save a template like:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXY
2) Note error preventing you from doing so
3) Apply this patch
4) Run updatedatabase.pl
5) Repeat step 1
6) Note you can now do so without error!

Signed-off-by: Charlie Arthur <carthur@slolibrary.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:23 -06:00
Andrew Fuerste Henry a6ef5415b3
Bug 42190: (QA follow-up) use num-html for itemnumber, biblionumber, borrowernumber, and cardnumber
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:22 -06:00
Jonathan Druart 01d93148e8
Bug 42190: Do not export the dropdown menu
for itemnumbers, biblionumbers, cardnumbers and borrowernumbers

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:22 -06:00
Jonathan Druart a2b75987de
Bug 42190: Use num-html for itemnumber and biblionumber columns
Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:21 -06:00
Jonathan Druart 040e17a608
Bug 42190: Add "Back to normal view"
Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:21 -06:00
Jonathan Druart 934e5429f4
Bug 42190: Remove the 'Configure' button
Remove the "Configure" if the table does not have module, page and table
defined.

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:20 -06:00
Jonathan Druart 97469d1a0d
Bug 42190: Add DataTables to report results
This is a POC (i.e. not ready for inclusion yet)
It adds a "Open in DataTables" button in the toolbar, that will refetch
the report (with a limit=100000, that needs to be configurable, how?)
and load the results in a kohaTable (DataTables)

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-24 11:43:13 -06:00
Tomás Cohen Arazi e7b258bdb7
Bug 19871: (QA follow-up) Remove safe_do, use translate_exception directly
The safe_do wrapper adds indirection without value:

- In Patron::Modification::approve, every operation inside safe_do
  goes through Object::store or Object::delete, which already call
  translate_exception internally. The wrapper was translating
  already-translated exceptions.

- In Item::add_to_bundle, the outer try/catch just called rethrow,
  making the safe_do + try/catch nesting a no-op.

This follow-up removes safe_do and aligns both callers with the
pattern already used by Object::store and Object::delete: call
translate_exception directly in the catch block when raw DBIC
operations are involved.

Changes:
- Remove safe_do from Koha::Schema (and its Try::Tiny import)
- Flatten add_to_bundle to use translate_exception in its catch
- Flatten Patron::Modification::approve to rely on Object::store
- Remove safe_do test, adjust test count

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:19 -06:00
Tomás Cohen Arazi 035d0fc277
Bug 19871: (QA follow-up) Add missing POD for methods in touched files
The QA tools flag all methods without POD in any touched file.
This patch adds the missing POD for pre-existing methods that were
flagged:

- Koha::Item::_add_statistic
- Koha::Object::_date_or_datetime_column_type
- Koha::Object::_datetime_column_type
- Koha::Object::_numeric_column_type
- Koha::Object::_decimal_column_type
- Koha::Object::_recursive_fixup
- Koha::Object::_get_object_class
- Koha::Object::DESTROY
- Koha::Patron::Modification::_type (also fixes heading mismatch)

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:18 -06:00
Martin Renvoize c7d3b9860e
Bug 19871: Make exception handling more explicit in add_to_bundle
Replace the implicit exception translation call with an explicit safe_do
pattern that clearly shows exceptions are rethrown.

Changes:
- Use $schema->safe_do() wrapper for automatic exception translation
- Add explicit $_->rethrow() in catch block for clarity
- Follow the same pattern used in Koha::Patron::Modification::approve()
- Make control flow more obvious to future maintainers

This change maintains identical behavior while improving code clarity
by making it explicit that the catch block never returns normally.

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:18 -06:00
Martin Renvoize 4425d77da5
Bug 19871: (QA follow-up) Replace symbolic refs with proper mock object class
Replace 'no strict refs' usage with a proper TestObject package to avoid
Perl::Critic violations while maintaining the same test functionality.

The mock object now uses standard OOP patterns instead of symbolic
references for method creation.

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:17 -06:00
Martin Renvoize 566790d3b2
Bug 19871: Move enum data truncation handling to centralized exception translation
Complete the migration of enum data truncation exception handling from
Koha::Object to the centralized ExceptionTranslator utility. This eliminates
the last remaining manual DBIx::Class exception handling in Object code.

Changes:
- Add optional object parameter to ExceptionTranslator->translate_exception()
- Update Schema wrapper methods to accept and pass object parameter
- Enhance enum truncation handling to extract actual property values when object is available
- Remove manual enum exception handling from Koha::Object::store()
- Add comprehensive test coverage for enum handling with and without object context
- Maintain contextual warning behavior for store() operations

The centralized system now handles ALL DBIx::Class exception scenarios:
- FK constraint failures (add/update and delete operations)
- Duplicate key violations
- Invalid data type values
- Enum data truncation with actual property values

This completes the centralization effort while providing better error reporting
than the previous manual approach.

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:17 -06:00
Martin Renvoize 98522293d6
Bug 19871: Update Koha::Patron::Modification to use centralized exception handling
Replace manual DBIx::Class::Exception handling in the approve() method with
the centralized exception translation system while preserving domain-specific
exception behavior.

Changes:
- Use $schema->safe_do() for database operations with automatic exception translation
- Wrap safe_do in try/catch to convert any exception to domain-specific Patron::Modification exception
- Simplify exception handling logic while maintaining the same API contract
- Remove manual DBIx::Class::Exception type checking

This maintains backward compatibility by always throwing
Koha::Exceptions::Patron::Modification exceptions as before, but leverages
the centralized system for consistent database exception handling.

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:16 -06:00
Martin Renvoize b04432f399
Bug 19871: Update virtualshelf templates to use Koha exceptions instead of DBIx::Class::Exception
Replace DBIx::Class::Exception handling in shelf templates with specific
Koha exception types that are now provided by the centralized exception
translation system.

Template changes:
- Replace 'DBIx::Class::Exception' case with specific Koha exception handlers
- Add user-friendly error messages for FKConstraint, DuplicateID, and BadValue
- Improve error presentation with descriptive text instead of raw database errors

This provides better user experience by showing meaningful error messages
instead of technical database exception details.

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:16 -06:00
Martin Renvoize 41d39199b2
Bug 19871: Move FK constraint deletion handling to centralized exception translation
Move the FK constraint deletion exception handling from Koha::Object::delete
to the centralized ExceptionTranslator utility. This consolidates all
DBIx::Class exception translation logic in one place and simplifies the
Object delete method.

Changes:
- Add FK constraint deletion pattern matching to ExceptionTranslator
- Remove manual exception handling from Koha::Object::delete
- Add test coverage for FK constraint deletion translation
- Simplify Object delete method to use centralized translation

The centralized system now handles both FK constraint scenarios:
- Child row constraint failures (add/update operations)
- Parent row constraint failures (delete operations)

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:15 -06:00
Martin Renvoize 17b117ca36
Bug 19871: Use centralized exception translation in Koha::Item::add_to_bundle
Replace duplicated DBIx::Class exception handling code with the new
centralized exception translation system introduced in the previous commit.

This change:
- Eliminates ~40 lines of duplicated exception handling code
- Uses the new Koha::Schema::translate_exception() method
- Maintains identical exception behavior and error messages
- Addresses the FIXME comment requesting this refactoring

The add_to_bundle method now uses consistent exception translation
with the rest of the codebase, reducing maintenance burden and
ensuring consistent error handling patterns.

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:15 -06:00
Martin Renvoize eb4a781da3
Bug 19871: Add centralized DBIx::Class exception translation system
This introduces a reusable exception translation utility that eliminates
code duplication and provides consistent handling of DBIx::Class exceptions
throughout the codebase.

New Components:
- Koha::Schema::Util::ExceptionTranslator: Core utility class for translating
  DBIx::Class exceptions to Koha-specific exceptions
- Koha::Schema: Enhanced with safe_do() and translate_exception() methods
  for convenient access to centralized exception handling
- Comprehensive unit tests covering all translation scenarios

Benefits:
- Eliminates ~90 lines of duplicated exception handling code
- Centralizes maintenance of exception translation logic
- Provides consistent error messages and exception types
- Makes it easier to extend support for additional database engines
- Follows the pattern suggested in existing FIXME comments

Exception Types Handled:
- Foreign key constraint violations → Koha::Exceptions::Object::FKConstraint
- Duplicate key violations → Koha::Exceptions::Object::DuplicateID
- Invalid data type values → Koha::Exceptions::Object::BadValue
- Enum data truncation → Koha::Exceptions::Object::BadValue

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:14 -06:00
Lucas Gass 5f442507f2
Bug 40659: DBRev 25.12.00.050
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:14 -06:00
David Cook adfe58328d
Bug 40659: (follow-up) Use UI language for custom virtual card layout
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:13 -06:00
David Cook 16288c9187
Bug 40659: (follow-up) prevent Javascript errors when SVG missing
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:13 -06:00
David Cook 609c11afc4
Bug 40659: (follow-up) include substitutions in the possible options for template
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:12 -06:00
David Cook ff9b74b72b
Bug 40659: (follow-up) add empty default VIRTUALCARD notice
Also include a reference to the notice template from sysprefs

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:12 -06:00
Lucas Gass 4db8cf6fc7
Bug 40659: Change Scrubber profile name to opac_virtual_card
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:11 -06:00
Lucas Gass e789a21fd5
Bug 40659: Unit tests for new Scrubber profile
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:11 -06:00
Lucas Gass ed950f1389
Bug 40659: Add Scrubber profile and scrub the contents of VIRTUALCARD template
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:10 -06:00
Lucas Gass e2d73f7926
Bug 40659: Add ability to customize OPACVirtualCard
To test:

1. Turn on the OPACVirtualCard.
2. Log in to the OPAC as a patron and go look at your Library card ( opac-virtual-card.pl )
3. APPLY PATCH, updatedatabase, restart_all
4. Go again at your patron's card on the OPAC ( opac-virtual-card.pl ), it should look exactly the same.
5. Go to Tools > Notices > VIRTUALCARD
6. You should see the default content for the library card.
7. Play around with different variables from borrower, like [% borrower.firstname%] and branches, like [% branch.branchcode %]
8. You can also move around the barcode by using the placeholder value [% my_barcode %].
9. Put the [% my_barcode %] variable anywhere you want in the notice then go to the sys pref "OPACVirtualCardBarcode".
10. Change to different barcode types and relaod the library card page ( opac-virtual-card.pl ). You should notice the barcode type is being honored.
11. Turn on OPACpatronimages AND patronimages, then upload a patron image for your patron.
12. Use the [% my_image %] variable anywhere you want in the notice to place the patron image.
13. Leaving the [% my_image %] variable as is, turn off OPACpatronimages.
14. Reload the OPAC page, no image should display.
15. Delete the VIRTUALCARD notice.
16. Go back to the OPAC page and with the notice deleted you should see the hardcoded content. ie: the page should look exactly the same as it does without this patch.

Signed-off-by: Marie Martino <martinom43@morainevalley.edu>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:10 -06:00
Lucas Gass 1d10da0e66
Bug 41311: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:09 -06:00
Lucas Gass dd5a0659cc
Bug 41311: DBRev 25.12.00.049
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:09 -06:00
Lucas Gass 6eb241bd7e
Bug 41311: (RM follow-up) Make DBRev match schema
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:08 -06:00
Kyle M Hall f351f5fbce
Bug 41311: (QA follow-up) Fix database update and table structure
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:08 -06:00
Nick Clemens 559f59a58f
Bug 41311: (QA follow-up) Adjust schema for boolean
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:07 -06:00
Kyle M Hall 4310aebeda
Bug 41311: Add ability for SIP to send patron home library ( branchcode ) in AO field
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:07 -06:00
Victor Grousset/tuxayo db1ab86bfe
Bug 39789: (QA follow-up) fix redundancy in koha-conf.xml
Some of the changes were already in the file.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:06 -06:00
Kyle M Hall 48783f4084
Bug 39789: Update koha-conf.xml
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:06 -06:00
Kyle M Hall ad18006dfc
Bug 39789: Add unit tests
Test Plan:
1) Apply this patch set
2) prove t/Koha/Middleware/RealIP.t

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:05 -06:00
Kyle M Hall 1c6f0b2eae
Bug 39789: Display the trusted proxy IP addresses and reverse proxy IP header on about page
Signed-off-by: Lin Wei Li <lin-wei.li@inLibro.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:05 -06:00
Kyle M Hall 13a4b44bc9
Bug 39789: Add ability to specify an alternative header to X-Forwarded-For for finding the real ip address
Some services ( such as CloudFlare ) use alternative heads to X-Forwarded-For. Koha should be able to use a custom header instead of X-Forwarded-For if needed.

This patch updates the feature to allow both the custom header and X-Forwarded-For to work harmoniously

For example, CloudFlare has it's own custom header.
This patch allows a site to be configured for Cloudflare's proxy before the proxy is enabled.

Test Plan:
1) prove t/Koha/Middleware/RealIP.t

Signed-off-by: Lin Wei Li <lin-wei.li@inLibro.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:04 -06:00
Jan Kissig 0a8182ba02
Bug 42289: (QA follow-up): Fix unreachable variable
This patch fixes the unreachable definition of 'let content'.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:03 -06:00
Jan Kissig b873dc56c0
Bug 42289: (QA follow-up): Split JS to seperate TT and tidy
This seperatetes the script into 2 parts in letter.tt. Auto tidy.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:03 -06:00
Jan Kissig e4b7b002ba
Bug 42289: (QA follow up) Remove perl comment and tidy
This changes a perl comment to a js comment. Auto tidy of JS

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:02 -06:00
Jonathan Druart a532a346bd
Bug 42289: Tidy all script tags - tools (auto tidy)
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:02 -06:00
Jonathan Druart 387a91eb58
Bug 42289: Tidy all script tags - tools
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the tools module.

The 2 main changes are in the following files:
* additional-contents.tt
* stage-marc-import.tt

Trivial changes.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:01 -06:00
Lucas Gass 281551d741
Bug 40633: DBRev 25.12.00.048
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:01 -06:00
Martin Renvoize 995bfe2083
Bug 40633: Open fixed-length field helpers from the advanced editor
The advanced cataloging editor lets users hand-edit MARC records as
text but offers no equivalent of the basic editor's value_builder
popups for the fixed-length fields (Leader, 006, 007 and 008).
Catalogers have to type those byte strings by hand, which is error
prone.

This patch wires the 'open_helper_plugin' shortcut (added in the
preceding atomic update) to a new handler in the advanced editor
which:

 - inspects the line under the cursor;
 - if it is a Leader, 006, 007 or 008 control field, opens the
   corresponding marc21_*.pl plugin from cataloguing/value_builder/
   in a popup window, pre-populated with the current value;
 - splices the plugin's returned value back into the CodeMirror buffer
   when the popup closes.

The plugin templates are reused unchanged: a hidden
<input id="adv_editor_fixedfield_buffer"> on the editor page satisfies
the existing report() callback contract, and the editor polls
window.closed to detect submission. The 008 plugin is given the
current Leader value via &leader= so its material-type byte map
matches the record being edited.

The shortcut description is added to keyboard_shortcuts.inc so it
appears with a meaningful label in the in-editor "show shortcuts"
popover and in Administration > Keyboard shortcuts.

Test plan:
1. Apply this patch on top of the preceding atomic update.
2. Visit Administration > Keyboard shortcuts and confirm
   'open_helper_plugin' is listed with description "Open fixed-length
   field helper (Leader, 006, 007, 008)" and that the key combination
   can be edited.
3. Open the advanced cataloging editor on any record:
   a. Place the cursor on the 000 (Leader) line and press Shift-Ctrl-H.
      The marc21_leader popup should open with the current Leader
      value. Make a change, click OK, and confirm the Leader line
      updates in the editor and is marked modified in the gutter.
   b. Repeat for 006, 007 and 008. For 008, change the Leader's
      material-type byte first and confirm the 008 popup form adapts.
   c. Place the cursor on a 245 (or any non-fixed-length field) line
      and press Shift-Ctrl-H. Nothing should happen.
   d. Open a popup, then close it without saving. The editor line
      should remain unchanged.
4. Save the record and confirm the new fixed-field values round-trip
   correctly when the record is reloaded.
5. Confirm the basic editor's value_builder popups still work
   unchanged on Leader / 006 / 007 / 008.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Jeanne Mauriello <jeanne.mauriello@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:04:00 -06:00
Martin Renvoize 37ad6039c6
Bug 40633: Add new keyboard shortcut for the fixed-length field helper
This adds a new 'open_helper_plugin' keyboard shortcut to the
keyboard_shortcuts table, used by the advanced cataloging editor to
open the value_builder popup for the Leader, 006, 007 and 008 fixed
length fields. The default key combination is Shift-Ctrl-H ("Helper")
and is rebindable from Administration > Keyboard shortcuts. The
behaviour bound to this shortcut is added in a follow-up patch.

Test plan:
1. Apply this patch and run installer/data/mysql/updatedatabase.pl in
   KTD.
2. Run:
     SELECT * FROM keyboard_shortcuts
      WHERE shortcut_name = 'open_helper_plugin';
   Confirm a row exists with shortcut_keys = 'Shift-Ctrl-H'.
3. Re-run updatedatabase.pl and confirm the atomic update is idempotent
   (no error, no duplicate row).
4. On a fresh install (or by re-loading mandatory/keyboard_shortcuts.sql
   on an empty table) confirm the same row is created.

Sponsored-by: Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Jeanne Mauriello <jeanne.mauriello@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 16:03:53 -06:00
Kabooshki f019e490f9
Bug 42106: Spelling: Failed to load plugin url: {0}
This patch fixes the url casing on both translation messages from "url"
to "URL" for better grammatical accuracy of the acronym.

Error not mentioned in original Bug Message hence replication is
undefined.

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-04-23 09:25:09 -06:00
Tom Yates dc644f0095
Bug 41516: Terminology: Change cardnumber to card number for system preference descriptions
Following the terminology guidelines, the descriptions for AutoSwitchPatron,
CardnumberLog and SelfCheckoutByLogin have been updated to use 'card number'
with a space.

Test plan:
1. Review the descriptions for the system preferences and note that 'cardnumber'
   is used, instead of 'card number' for:
   - AutoSwitchPatron
   - CardnumberLog
   - SelfCheckoutByLogin
2. Apply the patch.
3. Repeat step 1, and note that the system preferences now use 'card number'.

Signed-off-by: Tom Yates <tom@hire-tom.co.uk>
Signed-off-by: Andrew Auld <andrew.auld@openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:08 -06:00
Jonathan Druart be25cebf33
Bug 42423: Add a Cypress test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:08 -06:00
David Cook 57060de3c8
Bug 42423: Fix patron header search
This patch fixes the patron header search, so that you can do
patron searches from the header, but empty patron searches
are prevented and show a tooltip.

Test plan:
0. DO NOT APPLY THE PATCH YET
1. Log into the staff interface and try to search for anything
in the "Search patrons" header search
2. Note that nothing happens at all no matter how much you click

3. Apply the patch
4. Refresh the browser page
5. Try to search for anything in the "Seearch patrons" header search
6. Note that it performs the search
7. Empty out the input box and hit Enter or the search button
8. Note that a tooltip appears saying "Please enter a card number or name"

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:07 -06:00
Lucas Gass a9d5f7d453
Bug 42156: DBRev 25.12.00.047
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:07 -06:00
Martin Renvoize 77237445b1
Bug 42156: (QA follow-up) Expand unit tests for recovery and skip paths
The original test only verified that a warning was emitted when a corrupt
authority record was encountered; it did not assert that get_matches
returned the recovered record, that an unrecoverable record was dropped,
or that a stale index pointing at a deleted authid was handled.

This patch turns the 'get_matches bad record test' subtest into a full
exercise of the three code paths introduced on this bug:

 1 - Recoverable: a record with stray non-XML characters is repaired by
     record_strip_nonxml and returned in the match list with the expected
     authid.
 2 - Unrecoverable: record_strip_nonxml is mocked to return undef; the
     parse warning still fires but the record is silently dropped from
     the result set (no die).
 3 - Stale search index: search_auth_compat returns an authid that does
     not exist in auth_header; get_matches no longer dies and returns an
     empty result.

The MarcMatcher fixture is given an explicit threshold of 1 so the 1000
score on the authority matchpoint always passes and matches are returned.

Test plan:
 1 - prove t/db_dependent/Matcher.t
 2 - All subtests pass including the expanded 'get_matches bad record test'.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:06 -06:00
Martin Renvoize 3a3797f3f5
Bug 42156: (QA follow-up) Register the new 'authid' search field on upgrade
Adding an entry to admin/searchengine/elasticsearch/mappings.yaml is only
picked up by fresh installs or when an administrator explicitly resets the
search mappings (rebuild_elasticsearch.pl -r). Existing installations would
otherwise never see the new 'authid' index in the admin UI nor have it
available as a matching index until they ran the reset.

This atomicupdate inserts:
 - a search_field row for 'authid' (type 'stdno', staff_client + opac on)
 - search_marc_map rows for authorities/marc21/001 and authorities/unimarc/001
   (INSERT IGNORE - the rows are shared with 'local-number' and usually
   already exist)
 - search_marc_to_field rows linking those MARC maps to the new field with
   sort on, matching mappings.yaml

It is a no-op if 'authid' is already present.

Administrators still need to rebuild the Elasticsearch index for the new
field to become queryable - this is noted with say_info at the end.

Test plan:
 1 - On an existing instance without the patch: note there is no 'authid'
     field under Administration > Search engine configuration (Elasticsearch)
     for authorities.
 2 - Apply the whole patchset.
 3 - perl installer/data/mysql/updatedatabase.pl
 4 - Re-check the search engine configuration: 'authid' appears for the
     authorities index, mapped to 001 for both MARC flavours.
 5 - Run the update again - confirm the "already exists, skipping" warning.
 6 - misc/search_tools/rebuild_elasticsearch.pl -r -v -a
 7 - Confirm a matching rule using index 'authid' now works.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:06 -06:00
Martin Renvoize 9f1ae44cc7
Bug 42156: (QA follow-up) Correct POD typos and demote private helpers
The previous POD follow-up added headings for the private helper subs but
introduced several typos and used =head2, which puts them at the same level
as the public API in the generated docs. This patch:

 - Fixes typos: matchpint, pionts, march matcher, mathcpoints, etc.
 - Clarifies a couple of descriptions.
 - Demotes each private helper from =head2 to =head3 so they render as
   implementation notes rather than peers of the public methods, matching
   the pattern used in Koha::Authority's "Internal methods" block.

Helper subs remain interleaved with the public API in source order to keep
the diff minimal; a wholesale move into a dedicated "Internal functions"
section would be a larger refactor outside the scope of this bug.

Test plan:
 1 - perldoc C4/Matcher.pm
 2 - Confirm no typos are rendered and the private helpers appear one level
     deeper than the public methods.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:05 -06:00
Martin Renvoize 9d2b2c2493
Bug 42156: (QA follow-up) Guard against missing or unparseable authority records
The previous defensive block still dereferenced $target_auth unconditionally
when attempting to recover via record_strip_nonxml. If Koha::Authorities->find
returned undef (stale search index pointing at a deleted authid) the eval
around ->record would catch the first failure, but the recovery call on the
next line would die uncaught.

This patch:
 - Skips silently when the authid returned by the search engine no longer
   exists in the database, so get_matches cannot die on a stale index.
 - Wraps record_strip_nonxml in eval so an exception there is also handled
   and the record is simply skipped - matching the stated intent of the
   "skip when not possible" path.

Test plan:
 1 - Run t/db_dependent/Matcher.t - still passes.
 2 - Simulate a stale index: mock search_auth_compat to return an authid
     that is not present in auth_header. Confirm get_matches no longer dies
     and returns without error.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:05 -06:00
Nick Clemens 94a9712745
Bug 42156: (QA follow-up) Missing POD
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-04-23 09:25:04 -06:00
Nick Clemens f141799531
Bug 42156: Unit tests
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-04-23 09:25:04 -06:00
Nick Clemens c5adbe0818
Bug 42156: Attempt to repair corrupted records and skip when not possible
When matching authorities we can die on a bad record, we should add protection against this
on top of bug 41843

To test:
 1 - Apply index patch and rebuild elastic
    perl misc/search_tools/rebuild_elasticsearch.pl -r -v
 2 - Make a new record matching rule
    threshold: 1000
    type: authority
    index: authid
    score: 1000
    tag: 001
 3 - Choose an authority record and corrupt it:
    sudo koha-mysql kohadev
    UPDATE auth_header SET marcxml = UpdateXML(marcxml, '//datafield[@tag="040"]/subfield[@code="c"]',CONCAT('<subfield code',CHAR(27),'="c">OSt</subfield>')) WHERE auth_header.authid=92;
 4 - View and save the record as marcxml
    http://localhost:8081/cgi-bin/koha/authorities/detail.pl?authid=92
 5 - Cataloging - stage marc records - authority/marcxml/no matching
 6 - Confirm it stages
 7 - Attempt to match using your new rule
 8 - Get an error parsing the marc
 9 - Apply patch, restart all
10 - Relaod the staged batch
11 - Attempt matching agaion
12 - Success!

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-04-23 09:25:03 -06:00
Nick Clemens 88cfb35e5e
Bug 42156: Add a stdno index for authid
In order to allow matching on authid we need a search index with a ci_raw
field

local-number is indexed as a number, and needed that way for sorting. This adds
a stdno version of 001 as authid

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-04-23 09:25:03 -06:00
David Nind ec88f94c42
Bug 39748: (follow-up) Update number of tests
Tests were failing as the number of tests was incorrect
as bug 42085 added additional tests.

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:02 -06:00
David Cook 076c5bce8d
Bug 39748: Fix Koha::Patron->is_expired to calculate using floating timezone
This patch updates Koha::Patron->is_expired so that it uses the floating
timezone for its datemath calculation.

Test plan:
0. Apply only the first patch including the unit test
1. prove -v t/db_dependent/Koha/ILL/Request.t \
        t/db_dependent/Koha/Patron/Restriction.t \
        t/db_dependent/Koha/Patrons.t \
        t/db_dependent/Koha/AdditionalContents.t \
        t/db_dependent/ILSDI_Services.t
2. Note that the unit tests fail
3. Apply the patch including the fix
4. prove -v t/db_dependent/Koha/ILL/Request.t \
        t/db_dependent/Koha/Patron/Restriction.t \
        t/db_dependent/Koha/Patrons.t \
        t/db_dependent/Koha/AdditionalContents.t \
        t/db_dependent/ILSDI_Services.t
5. Note that all unit tests pass now

6. Bonus points:
- Add the following to the top of /etc/koha/sites/kohadev/plack.psgi
before Modern::Perl:
use Test::MockTime;
BEGIN {
    Test::MockTime::set_absolute_time('1745580600');
}

- koha-plack --restart kohadev
- Look at the About page and do some circulation
- Without the patches, you'll get an internal server error
- With the patches, it'll just work

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:02 -06:00
David Cook e0859f245f
Bug 39748: Add unit test
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:01 -06:00
Jonathan Druart 0fb44d7ab5
Bug 27826: Add a Cypress test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:01 -06:00
Tadeusz „tadzik” Sośnierz ec938d2a1d
Bug 27826: Escape regexes when checking for new items in sco-main.tt
Test plan:

1. In KTD, go to cgi-bin/koha/catalogue/detail.pl?biblionumber=11
2. Edit the item, set its biblionumber to "+12345"
3. Go to self-checkout at /cgi-bin/koha/sco/sco-main.pl, log in (42:koha)
4. Enter "+12345" into the "Scan a new item" input field, submit
5. Observe a crash
6. Apply patch
7. Refresh the page, observe no patch
8. Renew item, refresh a few times, to observe the "NEW" badge working as expected

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:00 -06:00
Owen Leonard b56148425b
Bug 41964: Upgrade the Multiple Select plugin in the staff interface to 2.3.1
This patch upgrades the Multiple Select plugin in the staff interface.
It was last updated in 2023.

The patch also moves some CSS from preferences.css into
staff-global.scss because it is globally applicable.

To test, apply the patch and rebuild the staff interface CSS. Clear your
browser cache if necessary. Test various pages which use the plugin to
confirm that it still works correctly:

- Administration -> Patron categories -> New -> Block expired patron
  OPAC actions
- Administration -> System preferences: Many, including
  ApplyFrameworkDefaults, MarkLostItemsAsReturned,
  decreaseLoanHighHoldsIgnoreStatuses, CoceProviders,
  OPACShowSavings, etc.
- ILL requests -> Filter by status (in the sidebar)

- Confirm that the "About" page shows the correct updated version
  number.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:25:00 -06:00
Casey Conlin d09c65d047
Bug 42359: Fix Guided.t update_sql test input
The test was feeding update_sql object field names instead of the parameter names it actually expects.
This let savedsql get cleared during the update.
The test was updated to pass the expected inputs so it still checks cache_expiry without creating the warning in the delete log path.

Test plan:
1. Confirm the issue by enabling the ReportsLog preference in Koha UI andin ktd shell running prove t/db_dependent/Reports/Guided.t
2. Apply the patch.
3. Run the tests again with prove t/db_dependent/Reports/Guided.t
4. The tests should pass.
5. Disable the preference and run again.
6. Tests should pass.
7. Sign off if steps above check out.

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:59 -06:00
Andreas Roussos d88274881e
Bug 21453: Copy data to the correct subfields when linking authorities with authorities
When using the authority finder plugin from within Koha's
Authorities module, you can pick an entry from the 'Special
relationship:' dropdown menu in order to set the relationship
between authorities.

The problem is that in UNIMARC setups, when you click on
'Choose' to select an authority, it does not populate
subfields $3 (Authority Record Number) and $5 (Tracing
Control), which are required for authority linking and
authority hierarchies to work properly.

In MARC21, all authority data is correctly copied over.

This is due to hardcoded subfield values which are
MARC21-specific in blinddetail-biblio-search{.pl,.tt}.

This patch fixes that by checking the value of the
'marcflavour' syspref, and then setting the correct
subfields to copy the authority data to.

Test plan (involves testing in both a MARC21 and a UNIMARC instance):

0) [PREREQUISITES in order for the authority finder plugin to come up
    when testing in a UNIMARC instance]
   a) You must have at least one authority type besides the 'Default'.
   b) View the MARC structure of the 'Default' auth type, then click
      on 'Actions->Subfields' for tag 500; click on 'Edit subfields'.
      Select the subfield 'a' tab, then pick an auth type from the
      'Thesaurus:' dropdown menu; click 'Save changes' at the top.

1) In the Staff client, go to Authorities, then click 'New Authority'
   and pick the 'Default' authority type.

2) When the authority editor comes up, go to tag 500 and expand it.

3) Click on the Tag editor icon next to subfield $a.

4) When the authority finder comes up, search for an authority, pick
   a relator code from the 'Special relationship:' dropdown, and then
   click on the 'Choose' button for one of the authorities you found.

5) In MARC21: all subfields are populated correctly.
   In UNIMARC: notice how subfields $3 and $5 are *not* populated.

6) Apply the patch.

7) Repeat steps 3) and 4) above.
   In MARC21: all subfields are populated correctly, as before.
   In UNIMARC: both the relator code and the authority id are copied
               over along with any other subfields that may exist.

8) To ensure that authority linking in the Cataloging module works
   as before, launch the bibliographic record editor, find a MARC
   tag that allows you to search for an authority, and link it.
   In MARC21: all authority data is copied over (authid in $9).
   In UNIMARC: all authority data is copied over (authid in $9).

   NOTE: after linking, you may have to expand the MARC tag you
         chose so that subfield $9 becomes visible.

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:58 -06:00
Saiful Amin 1f325d5799
Bug 41353: koha-dump failing on mysqldump PROCESS privileges
Problem:
Starting from MySQL 5.7.31 and MySQL 8.0.20, mysqldump requires the
PROCESS privilege to access tablespace metadata by default. Without
this privilege, backup operations fail with permission errors.

Solution:
Add the --no-tablespaces flag to the dbflag variable. This prevents
mysqldump from attempting to access tablespace metadata, allowing
backups to complete successfully without requiring the PROCESS privilege.

Test Plan:
  1. Set up a MySQL instance (5.7.31+ or 8.0.20+) without PROCESS privilege for the Koha database user
  2. Run the koha-dump script for a Koha instance:
     sudo koha-dump kohadev
  3. Notice the backup fails with a PROCESS privilege error
  4. Apply the patch
  5. Copy debian/scripts/koha-dump to /usr/sbin/koha-dump
  6. Repeat step 2
  7. Notice the backup now completes successfully without permission errors
  8. Test restoring the dump to ensure it's valid:
     zcat /var/spool/koha/kohadev/kohadev-<date>.sql.gz | tail
  9. Sign-off

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:58 -06:00
Caroline Cyr La Rose b74f544231
Bug 42309: JS error when there are no cash registers
This patch corrects a JavaScript error that appears on the cash
registers page when there are no cash registers yet.

To test :
BEFORE applying the patch
0. Make sure there are no cash registers in the database (reset_all if using ktd, or delete them)

1. Enable UseCashRegisters
   1.1. Go to Administration > System preferences
   1.2. Search for UseCashRegisters
   1.3. Set UseCashRegisters to Use
   1.4. Click Save all Accounting preferences

2. Open the browser developer tools (F12) and select the Console tab

3. Go to Administration > Cash registers
   --> Uncaught TypeError: can't access property "DataTable", crtable is undefined

4. Apply the patch

5. Refresh the cash registers page
   --> The error is gone!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:57 -06:00
Jonathan Druart da121583a4
Bug 42277: Remove JS error when viewing a subscription
If no orders exist yet, there is a JS error in the console:
Uncaught TypeError: can't access property "DataTable", table is
undefined

Test plan:
0. Do no apply this patch
1. Create a new subscription and go to the detail view
2. Open the console and notice the JS error
3. Apply this patch
4. Confirm that the JS error is gone
5. Create an order from this subscription
6. Confirm that the orders table is displayed

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:57 -06:00
Jonathan Druart be9efe452d
Bug 42341: Fix translatability of 'Print label' on staff detail page
To test:
- Activate SpineLabelShowPrintOnBibDetails
- Install any translation. For ktd:
  sudo koha-translate --install de-DE --dev kohadev
- Activate translation with StaffInterfaceLanguages
- Find a bibliographic record with items and callnumbers in staff
- Verify the "Print label" is translated when switching between languages

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:56 -06:00
David Cook 213693ed4f
Bug 42163: (follow-up) fix plugin wrapper menus, plugins-home.tt, and plugins-upload.tt
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:56 -06:00
Lisette Scheer 63695f05d5
Bug 42163: Allow plugin wrapper to load VueJS
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:55 -06:00
Jonathan Druart 48b0be1f52
Bug 42003: Remove !important from css files
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:55 -06:00
Jonathan Druart 5c38cc7d7c
Bug 42003: DT - Update css and js files to 2.3.7
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:54 -06:00
Jonathan Druart b240cff51d
Bug 40816: Ignore 'ResizeObserver loop limit exceeded'
I have NO idea how to fix this error. I don't see it on the Koha UI,
neither using Cypress UI.

It seems safe for now to ignore this error (The internet says so).

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:54 -06:00
Jonathan Druart 237d3deecf
Bug 40816: Fix Auth/csrf.ts
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:53 -06:00
Jonathan Druart 004c2419a9
Bug 40816: Fix a failing Selenium test
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:53 -06:00
Owen Leonard 3510157168
Bug 40816: (follow-up) Standardize sort icon alignment
DataTables wants to change the alignment of the sorting icons when it
detects the column contents as numeric or date. This patch overrides
that.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:52 -06:00
Owen Leonard 6fa09e9e9c
Bug 40816: (follow-up) CSS updates for table sorting icons
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:52 -06:00
Jonathan Druart 69ce43ef06
Bug 40816: Adjust about page
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:51 -06:00
Jonathan Druart ef69e07236
Bug 40816: Remove !important from css files - OPAC
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:50 -06:00
Jonathan Druart 04168c8e87
Bug 40816: DT - Update css and js files - OPAC
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:50 -06:00
Jonathan Druart 8cc059f347
Bug 40816: Remove !important from the min version
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:49 -06:00
Jonathan Druart b2d27e2f5a
Bug 40816: Fix sort icons
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:49 -06:00
Owen Leonard 596c764b3a
Bug 40816: Cherry-pick previous manual changes after update
This is a cherry-pick of
  commit e0193051e1
  Bug 36640: (follow-up) CSS fixes for DataTables controls

I have only kept the changes to datatables.css

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:48 -06:00
Jonathan Druart 97c8fbf914
Bug 40816: DT - Update css and js files
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:48 -06:00
Raphael Straub 43d32f77d6
Bug 41062: Add an option to erm_run_harvester.pl to specify provider IDs
This patch adds a new option to define the providers in cronjob erm_run_harvester.pl
that are used to run the SUSHI harvesting for COUNTER Reports in the ERM module.
Use parameter --provider-id or -p
The parameter provider_id is repeatable.
If the parameter for provider_id is not used all active providers will be harvested (as before).
The script will check if these providers are active (as before).

Script to run on ktd: perl misc/cronjobs/erm_run_harvester.pl --begin-date 2025-09-01 --end-date 2025-09-30

To test:
- Run the script and make sure that all active providers are harvested.
- Apply patch.
- Run the script with only one provider-id.
- Run the script with two provider-ids by using -p 1 -p 2
- Run the script without the parameter.
- Make sure that in all cases only active providers are harvested.
- Sign off :-).

Sponsored-by: Karlsruhe Institute of Technology (KIT)
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-23 09:24:47 -06:00
Lucas Gass 3fd93025ec
Bug 40255: (follow-up) Fix failure in add_debit tests
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-20 13:04:58 -06:00
Jonathan Druart 3a91b4f8d0
Bug 35267: Fix check_sysprefs.t
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-20 13:04:58 -06:00
Jonathan Druart 1c60030164
Bug 21820: Fix Koha/SearchEngine/Indexer.t
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-20 13:04:51 -06:00
Paul Derscheid a81b2cfe4f
Bug 16994: (QA follow-up) Align overwrite result key across module, template and tests
The import result hash key was inconsistent: the module sets
$result->{overwrote}, but the test checked $result->{overwrite} and the
template wrapper used import_overwrite. The uninitialized key also
triggered a "Use of uninitialized value" warning when no templates were
overwritten.

- Initialize overwrote => 0 in ImportModificationTemplates result hash
- Update test to check $result->{overwrote}
- Fix template success-alert wrapper to gate on import_overwrote

Test plan:
- prove t/db_dependent/MarcModificationTemplates.t
- all 134 subtests pass

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:17 -06:00
Arthur Suzuki 7e1aff13ef
Bug 16994: Add unit tests
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:16 -06:00
Arthur Suzuki 33dc9ac24a
Bug 16994: Import and export MARC modification templates
This patch adds export and import functions to the MarcModificationTemplates tools
The different templates can be exported either individually or all of them.
On import you can choose to either skip or overwrite existing templates with the same name.

Test plan:
- create a Marc Modification templates with one field creation action.
- click on "Export all" in the menu and save the file on your computer.
- add a new action in the template (ie, update field).
- click on "Import" in the menu and choose the downloaded file.
- by default the import tool doesn't overwrite
- there should be a message saying "skipped 1 template"
- the template should still have 2 actions.
- import the same file again but uncheck the "skip existing template".
- this time the template should be back to only one action.
- delete the template
- import the same file again, the template should be back with a single action.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:16 -06:00
Owen Leonard 04ca348222
Bug 42356: Silence Sass if-function deprecation warning
Like Bug 41327, deprecation warnings about Bootstrap SCSS should be
silenced with the assumption that a future version of Bootstrap will
correct the problems. In this case the warning is:

Deprecation Warning [if-function]: The Sass if() syntax is deprecated in
favor of the modern CSS syntax.

To test, apply the patch and run 'yarn build.' There should be no
warnings.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:15 -06:00
Andrew Fuerste Henry a60b4e8389
Bug 40255: (QA follow-up) Adjust test count
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:15 -06:00
Lucas Gass 5fc7fbbb21
Bug 40255: Allow scrubbed HTML to be used
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:14 -06:00
Lucas Gass 6b93327e6c
Bug 40255: Fixes for translations and updated unit tests
Signed-off-by: Scott Aylett <saylett@spanishfork.gov>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:14 -06:00
Lucas Gass 9a6a5a01f0
Bug 40255: (follow-up) Fix errors in letter.tt
Signed-off-by: Scott Aylett <saylett@spanishfork.gov>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:13 -06:00
Lucas Gass 7820875efe
Bug 40255: Unit test
Signed-off-by: Scott Aylett <saylett@spanishfork.gov>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:12 -06:00
Lucas Gass 1f1e0c677d
Bug 40255: Allow for custom debit descriptions
To test:

1. Apply patch, restart all
2. Go to debit types and click "Show all debit types" and pick a system defined debit type to test with. I worked mainly with ACCOUNT ( Account creation fee ) but we should test with as many debit types as possible
3. Note the account_debit_types.code you are working with
4. Now go to Tools > Notices and Slips and create a new notice
5. Choose the "Debit description (custom)" module as your module.
6. Use the code from step 2 and 3.
7. In the "email" part of the noctice add something like:
    $[% accountline.amount %] (includes $[% accountline.amount * 0.1 %] tax)

8. Now go to  Administration > Patron categories and configure a patron category to have a "Enrollment fee". ( I set mine to 100 )
9. Now create a patron in that category.
10. Once created, visit the accounting tab for that patron.
11. You should see a description like "$100.00 (includes $10.00 tax)"
12. Try with other debit types to ensure this works with all.

Signed-off-by: Scott Aylett <saylett@spanishfork.gov>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:12 -06:00
Lucas Gass aaf2959023
Bug 35267: DBRev 25.12.00.046
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:11 -06:00
Jonathan Druart a241b866ba
Bug 35267: Ignore style tags with TT tags
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:11 -06:00
Martin Renvoize 6f96128f5b
Bug 35267: (follow-up) Add POD coverage for private subroutines
Add POD documentation for private subroutines in C4/Letters.pm and
fix the POD heading for _type in Koha/Notice/Message.pm to satisfy
the pod_coverage QA check.

Also fixes a typo in the existing _add_attachments POD heading
(_add_attachements → _add_attachments).

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:10 -06:00
Thomas Klausner 329b0da16a
Bug 35267: (follow-up) Adapt atomicupdate to new DB schema
and add a missing newline

Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:10 -06:00
Martin Renvoize a1b7017588
Bug 35267: (follow-up) Fix PrintSlipCSS accordion and CSS in HTML output
- Add missing PrintSlipCSS accordion panel to the notice editor
- Pass AllNoticeCSS and PrintNoticeCSS to gather_print_notices.pl HTML output
- Include inline <style> blocks in print-notices.tt template

Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:09 -06:00
Martin Renvoize eb357bcffa
Bug 35267: (follow-up) More comprehensive and db agnostic tests
This patch updates our unit tests to be more agnostic to prior database
state during testing by mocking more of the preferences.

We also add more tests to ensure we fully cover the new preferences and
their options.

Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:09 -06:00
Martin Renvoize b6506f8e54
Bug 35267: (follow-up) Remove 'apply to all' from format
With the introduction of more granular global preferences for notice CSS
we no longer need the somewhat confusing per notice but apply to all
format checkbox. As suggested by Lucas, this patch removes that option.

This is safe to remove at this stage as the feature in question isn't
really working given the second bug implimenting most of the
functionality to expose it is still in the queue.

Sponsored-by: Open Fifth <https://openfifth.co.uk>
Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:08 -06:00
Martin Renvoize 308fdcec48
Bug 35267: (follow-up) Add explanatory note to CSS displays
This patch adds a simple explanatory note to each of the Notice CSS
display options in the notices template editor page

Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:08 -06:00
Martin Renvoize 732667d5f3
Bug 35267: Update unit tests
This patch simply updates the existing unit tests to rename NoticeCSS to
AllNoticesStylesheet as per the rest of the patchset.

Sponsored-by: Open Fifth <https://openfifth.co.uk>
Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:07 -06:00
Martin Renvoize 38dcf70e5c
Bug 35267: Migrate accounts_summary to printslip
This patch migrates the 'slip' printing for the ACCOUNTS_SUMMARY slip to
circ/printslip as this is a slip rather than a notice.

Sponsored-by: Open Fifth <https://openfifth.co.uk>
Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:07 -06:00
Martin Renvoize ca0195e448
Bug 35267: Move JS to bottom in printslip
Sponsored-by: Open Fifth <https://openfifth.co.uk>
Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:06 -06:00
Martin Renvoize 49651ddaf0
Bug 35267: Apply preferences to preview and mailing
This patch applies the new style preferences to the preview display and
email sending code.

Sponsored-by: Open Fifth <https://openfifth.co.uk>
Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:06 -06:00
Martin Renvoize 74d008d01e
Bug 35267: Expose style preferences in template editor
Sponsored-by: Open Fifth <https://openfifth.co.uk>
Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:05 -06:00
Martin Renvoize 59709ecc19
Bug 35267: Add preferences to UI
Sponsored-by: Open Fifth <https://openfifth.co.uk>
Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:05 -06:00
Martin Renvoize c1314fd57b
Bug 35267: Add and Update Notice CSS system preferences
This patch clarifies the system level notice css preferences.

We rename NoticeCSS to EmailNoticeStylesheet
We rename SlipCSS to PrintSlipStylesheet
We add AllNoticeStylesheet
We add AllNoticeCSS to contain inline CSS for all html notices
We add EmailNoticeCSS to contain inline CSS for Email notices
We add PrintNoticeCSS to contain inline CSS for Print notices
We add PrintSlipCSS to contain inline CSS for Print slips

Sponsored-by: Open Fifth <https://openfifth.co.uk>
Signed-off-by: Marion Durand <marion.durand@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:04 -06:00
Martin Renvoize f20428ab1f
Bug 37713: (QA follow-up) Add unit tests
Add tests for OpacSuppression handling in OAI-PMH
get_biblio_marcxml(), covering the full preference/flag matrix:
- Preference disabled, flag set: record returned
- Preference enabled, flag unset: record returned
- Preference enabled, flag set: record hidden
- Preference disabled again: record returned

Also cleans up stray 'use DDP' and extracts a
_build_repository() helper to reduce duplication.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:04 -06:00
David Cook 70ff8dd1b4
Bug 37713: Do not show record metadata if OPAC suppressed
This change checks the 942$n if OpacSuppression is enabled,
and does not return the record metadata if 942$n is true. See BZ 31161.

Test plan:
0. Apply the patch
1. koha-plack --restart kohadev
2. Enable syspref "OAI-PMH"
3. Set syspref "OpacSuppression" to "Hide"
4. Go to http://localhost:8080/cgi-bin/koha/oai.pl?verb=ListRecords&metadataPrefix=oai_dc
5. Note KOHA-OAI-TEST:2 metadata visible
6. Go to http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=2
7. Set 942$n to "Yes"
8. Go to http://localhost:8080/cgi-bin/koha/oai.pl?verb=ListRecords&metadataPrefix=oai_dc
9. Note KOHA-OAI-TEST:2 metadata no longer visible and instead "Record Status: deleted" shows

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:03 -06:00
Emily Lamancusa 29caec496b
Bug 41851: (QA follow-up) Log completion before exiting
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:03 -06:00
Nick Clemens 18ffe25210
Bug 41851: Add logging to edi cron job
To test:
1 - Enable cronjob logging:
    http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=CronjobLog
2 - Enable EDIFACT
    http://localhost:8081/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=EDIFACT
3 - perl misc/cronjobs/edi_cron.pl
4 - Check action logs for cronjobs:
    http://localhost:8081/cgi-bin/koha/tools/viewlog.pl?src=&user=&modules=CRONJOBS&actions=&object=&info=&interfaces=&from=&to=&output=screen&basename=Export&report_name=&do_it=1
5 - No log
6 - Apply patch
7 - perl misc/cronjobs/edi_cron.pl
8 - Refresh the action logs screen
9 - Success! Start and end logged

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:02 -06:00
Owen Leonard 22fa60828f
Bug 40727: (alternate) Remove unnecessary .actions CSS
This patch takes an alternate approach of removing some CSS which isn't
strictly necessary. I think there was a time when the Bootstrap buttons
didn't come with their own margins and we needed to add it manually.

This change will slightly affect the margins of any buttons inside a
td.actions, but the default margin looks fine to me. Examples:

- Administration -> Patron categories
- Tools -> Notices and slips

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>
Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:02 -06:00
Jonathan Druart cb04b4c691
Bug 42288: Tidy all script tags - reserve (auto tidy)
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:01 -06:00
Jonathan Druart 0d48e33728
Bug 42288: Tidy all script tags - reserve
In order to tidy all the script tags of the .tt files we isolate the
interpolation of Template::Toolkit variables in a separate script tag.

This patch focuses on the reserve/hold module.

The changes are trivial but make sure there is no JS errors/warnings in
the console when placing a hold on an item. Also the table settings need
to be taken into account.

Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:01 -06:00
Jonathan Druart c5a34818de
Bug 41653: Move addPrefs and addPermissions under the Koha namespace
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:00 -06:00
Jonathan Druart 49271d8d19
Bug 42394: Fallback to {} if no userenv set
It fixes the following warnings:
  Can't use an undefined value as a HASH reference at /kohadevbox/koha/Koha/BackgroundJob.pm line 128

I started to add mock_userenv to all our tests that enqueue but finally
found a test in t/db_dependent/Koha/BackgroundJob.t that expect an
userenv not set
102     is( $job->borrowernumber, undef, 'No userenv, borrowernumber undef' );

So falling back to {} seems safe for now.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:18:00 -06:00
Martin Renvoize 1c89551270
Bug 42394: (QA follow-up) Clean up test
Remove unused $patron variable and add description string
to is_deeply assertion for TAP output readability.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:17:59 -06:00
Jonathan Druart 98ac202ddc
Bug 42394: Shallow-copy userenv in BackgroundJob->enqueue
C4::Context->userenv returns a direct reference to the singleton
userenv hash. enqueue() was mutating this reference — deleting
session_id and adding an interface key — which corrupted the
session state for the remainder of the request.

See bug 40943 comment 32 for a detailed explanation of the issue.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:17:59 -06:00
Jonathan Druart 5cef972961
Bug 42394: Add regression test for userenv mutation during enqueue
Verifies that BackgroundJob->enqueue() does not mutate the
C4::Context userenv hashref as a side-effect.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:17:58 -06:00
Jonathan Druart 5288847641
Bug 38136: Clear localization cache before testing itemtype localization
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:17:58 -06:00
Lucas Gass 6e2554c35f
Bug 38136: (follow-up) Add property to Localization call in t/db_dependent/api/v1/items.t
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-17 09:17:51 -06:00
Pedro Amorim 5040517ce1
Bug 41955: Show hold group id in OPAC holds history
Test plan:
1) Enable DisplayAddHoldGroups and OPACHoldsHistory sys prefs
2) Go to 'koha' user and click 'Search to hold':
<staff_url>/cgi-bin/koha/members/moremember.pl?borrowernumber=51
3) Search for 'test' and click the top 'Select all' link
4) Click the downward arrow in the 'Place hold' button and pick 'Place hold for Henry'
5) Set a pickup location for each hold and tick the 'Treat as hold group' checkbox
6) Click 'Place holds'.
7) Check-out an eligible barcode e.g.: 39999000005578 to Henry
8) Login on the OPAC as 'koha' user and visit holds history:
<opac_url>/cgi-bin/koha/opac-holdshistory.pl
9) Confirm that a new 'Hold group ID' column exists.
10) Disable DisplayAddHoldsGroups sys prefs. Confirm table looks as expected without the column.

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:13 -06:00
Pedro Amorim c084a3141a
Bug 41954: Display historic group hold ID in patron holds history table
Test plan:
1) Enable DisplayAddHoldGroups sys prefs
2) Go to 'Henry' and click 'Search to hold':
<staff_url>/cgi-bin/koha/members/moremember.pl?borrowernumber=19
3) Search for 'test' and click the top 'Select all' link
4) Click the downward arrow in the 'Place hold' button and pick 'Place hold for Henry'
5) Set a pickup location for each hold and tick the 'Treat as hold group' checkbox
6) Click 'Place holds'.
7) Check-out an eligible barcode e.g.: 39999000005578 to Henry
8) Visit Henry's holds history:
<staff_url>/cgi-bin/koha/members/holdshistory.pl?borrowernumber=19
9) Confirm that a new 'Hold group ID' column exists.
10) Disable DisplayAddHoldsGroups sys prefs. Confirm table looks as expected without the column.

Run all hold groups tests:
prove t/db_dependent/Koha/Patron.t
prove t/db_dependent/api/v1/patrons_hold_groups.t
prove t/db_dependent/Koha/Hold.t
prove t/db_dependent/Reserves/HoldGroup.t

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:12 -06:00
Pedro Amorim 7d574453ee
Bug 41954: Preparation: No longer delete 'empty' hold groups
Hold groups are now only deleted if no holds or old_holds are attached at the moment of cleanup

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:12 -06:00
Pedro Amorim fd4fa7c2d1
Bug 41954: Update existing tests
The logical behavior is different. Hold groups are now meant to be kept for historical reasons

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:11 -06:00
Pedro Amorim b0bb216310
Bug 41247: (QA follow-up): Improve uninstalling plugin
Ensure uninstalling works for the correct plugin if multiple plugins are installed

cypress run --spec t/cypress/integration/ILL/Batches_spec.ts

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:11 -06:00
Pedro Amorim 3c3ff65ab5
Bug 41247: (QA follow-up): Update cypress test
This cleans up the now removed XML changes and improves the clean-up process to only delete the illbatches created during testing as well as uninstalling the plugin through the UI instead of all plugins through a database query

The original XML based implementation is no longer relevant as plugins_restricted is now default 0 in ktd.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:10 -06:00
Pedro Amorim f1059643ab
Bug 41247: Add cypress tests
Apply just the 2 cypress tests patches, run:
cypress run --spec t/cypress/integration/ILL/Batches_spec.ts

Notice it fails.
Apply the fix patch. Repeat the command above. Tests must pass.

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:10 -06:00
Pedro Amorim e94cede03f
Bug 41247: Improve ILL batches modal resetability
Test plan, preparation:
A) Enable ILLModule
B) Install a metadata enrichment plugin, e.g:
https://github.com/openfifth/koha-plugin-api-pubmed/releases/tag/1.1.1
C) Restart plack to pick up the newly installed plugin if needed.
D) Create a new batch, visit:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?

Issue #1:
1.1) Click 'New ILL requests batch'
1.2) Enter a name, '42' on the cardnumber and any library. Click 'Continue'.
1.3) Click the 'Close' button (or click anywhere outside the modal) to close the modal.
1.4) Repeat 1.1). Notice the 'Continue' button is no longer there.

Issue #2:
2.1) Click 'New ILL requests batch'
2.2) Enter a name, '42' on the cardnumber and any library. Click 'Continue'.
2.3) Enter '123' on the textarea, click 'Process identifiers'.
2.4) Notice a 'Add items to batch' buttons appears. Don't click that, as before, close the modal.
2.5) Repeat 2.1) Notice the progress bar shows right off the bat (it shouldn't) with NaN as its value as well as table elements (export button, 'No entries to show' message, etc, with no table.

Apply patch, hard-refresh the browser and repeat Issues 1 and 2. Confirm the patch fixes them.

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

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:09 -06:00
Martin Renvoize fc6e5839d4
Bug 42221: Fix EAN-13 autoBarcode not incrementing
The EAN-13 barcode query used max(abs(barcode)) which allowed non-EAN-13
barcodes (e.g. 14-digit KTD sample barcodes) to dominate the result.
The saved 13-digit EAN-13 barcodes were always smaller, so $nextnum
never advanced and the same barcode was generated on every page load.

Fix by extracting EAN-13 logic into C4::Barcodes::ValueBuilder::EAN13
(consistent with other barcode type packages) and scoping the query to
WHERE length(barcode) = 13 so only valid-length barcodes contribute to
the max. Both barcode.pl and Koha::REST::V1::Biblios now call the shared
function, eliminating the duplicated inline code.

Tests: adds EAN-13 test cases to t/db_dependent/Barcodes_ValueBuilder.t
Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:09 -06:00
Martin Renvoize 757526cbde
Bug 42085: Slim down members/member-flags.pl cud-newflags block
Replace the raw-DBI permission write block (~62 lines) with a call to
$patron->set_permissions(\%granted). Logging and the superlibrarian
guard are now handled inside the object method.

The CGI layer now only parses the flag params into a granted hashref
and delegates — keeping the controller thin and the logic testable.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:08 -06:00
Martin Renvoize d8062a1fe6
Bug 42085: Log patron permissions in MEMBERS/DELETE action log
When BorrowersLog is enabled, Koha::Patron::delete() now captures the
patron's granted permissions before deletion and includes them in the
MEMBERS/DELETE log entry.

Patrons with no granted permissions produce no permissions key in the
log. Patrons with permissions will show them as removed in the diff
viewer, consistent with all other fields in the DELETE diff.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:08 -06:00
Martin Renvoize e32a108427
Bug 42085: Add Koha::Patron::set_permissions method
This new method moves the permissions write logic out of the CGI
controller and into Koha::Patron, making it testable and reusable.

Behaviour:
- Accepts a hashref of granted permissions only (matches the non-zero
  subset of permissions() return value)
- Computes the borrowers.flags bitmask from the userflags table
- Replaces user_permissions rows atomically inside a txn_do block
- Applies a session-aware superlibrarian guard when
  ProtectSuperlibrarianPrivileges is enabled
- Logs a MEMBERS/MODIFY action when permissions change and BorrowersLog
  is enabled, diffing before/after states

Tests added to t/db_dependent/Koha/Patrons.t.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:07 -06:00
Ayoub Glizi-Vicioso 0c2ef0e6b4
Bug 41967: Ensure --cards argument accepts an integer in cleanup_database.pl
Test plan (for --cards arguments) :

1. Initial Setup:
    a) Go to Tools > Patron card creator > New > Card batch
    b) Create two different batches (Batch 1 and Batch 2) using the 'Add by borrowernumber(s):' option.
        If using **KTD** you can use the following borrowernumbers for both batches:
            15
            29
            47
        else you will need to create your own patrons and enter their borrowernumber.
    c) In your terminal, set the timestamps manually:
        --OPTION A (KTD users):
            ktd --shell
            koha-mysql kohadev

        --OPTION B:
            Access your database

        Then:
            UPDATE creator_batches SET timestamp = DATE_SUB(NOW(), INTERVAL 30 DAY) WHERE batch_id = '1';
            UPDATE creator_batches SET timestamp = DATE_SUB(NOW(), INTERVAL 5 DAY) WHERE batch_id = '2';

    Note: your batch_id may differ.

2. Before Applying the Patch:
    a) Run the following cleanup script:
        ./misc/cronjobs/cleanup_database.pl --cards 15 --confirm --verbose
    b) Error in console output
    c) Nothing is deleted even though one batch was older than 15 days
        SELECT * FROM creator_batches;

3. Apply The Patch

4. After Applying The Patch:
    a) Redo step 1 (Initial Setup).
    b) Run again the following cleanup script:
        ./misc/cronjobs/cleanup_database.pl --cards 15 --confirm --verbose
    c) Check that the console output says: "Purging ... more than 15 days ago"
    d) Batch 1 is deleted, but Batch 2 (5 days old) is STILL PRESENT. Success!
        SELECT * FROM creator_batches;

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:07 -06:00
Ayoub Glizi-Vicioso 9e05ca0e19
Bug 41967: Ensure --labels argument accepts an integer in cleanup_database.pl
Test plan :

1. Initial Setup:
    a) Go to Cataloging > Label creator > New > Label batch
    b) Create two different batches (Batch 1 and Batch 2) using the 'Add by barcodes or itemnumbers' option.
        If using **KTD** you can use those barcodes for the two batches:
            39999000010114
            39999000010138
            39999000010152
        else you will need to create your own records with items and enter their barcodes.
    c) In your terminal (in a ktd --shell), set the timestamps manually:
        --OPTION A (KTD users):
            ktd --shell
            koha-mysql kohadev

        --OPTION B:
            Access your database

        Then:
            UPDATE creator_batches SET timestamp = DATE_SUB(NOW(), INTERVAL 30 DAY) WHERE batch_id = '1';
            UPDATE creator_batches SET timestamp = DATE_SUB(NOW(), INTERVAL 5 DAY) WHERE batch_id = '2';

    Note: your batch_id may differ.

2. Before Applying the Patch:
    a) Run the following cleanup script:
        ./misc/cronjobs/cleanup_database.pl --labels 15 --confirm --verbose
    b) Check that the console output says: "Purging ... more than 1 days ago"
    c) Check database and notice that it is now empty, even though one set was less than 15 days old.
        SELECT * FROM creator_batches;

3. Apply The Patch

4. After Applying The Patch:
    a) Redo step 1 (Initial Setup).
    b) Run again the following cleanup script:
        ./misc/cronjobs/cleanup_database.pl --labels 15 --confirm --verbose
    c) Check that the console output says: "Purging ... more than 15 days ago"
    d) Batch 1 is deleted, but Batch 2 (5 days old) is STILL PRESENT. Success!
        SELECT * FROM creator_batches;

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:06 -06:00
Johanna Räisä ae3fd5591f
Bug 42343: Prevent holds JS error when no holds exist
In the holds view, opening a record with no existing holds can leave
holdsQueueTable undefined while follow-up code still calls api() on it.

This triggers a Firefox console error:

  Uncaught (in promise) TypeError: can't access property "api", holdsQueueTable is undefined

Test plan:
1. Navigate to a title that has no existing holds.
2. Confirm no holdsQueueTable undefined error is logged.
3. Navigate to a title that has existing holds.
4. Confirm the holds queue still loads normally.
5. Confirm hold actions that refresh the queue still work as expected.

Sponsored-by: Koha-Suomi Oy

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:06 -06:00
Lucas Gass d71a40dc9d
Bug 42353: (follow-up) Add .nvmrc to Makefile
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:54:05 -06:00
Lucas Gass e86c33edd0
Bug 17387: (RM follow-up) Make Permissions.t tests pass
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-16 15:53:58 -06:00
Kyle M Hall 14b478bde7
Bug 39164: (QA follow-up) Add setting to koha conf files
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:33 -06:00
Mark Hofstetter 07ee120d50
Bug 39164: Add max_statement_time to SQL report queries
Reports may consume too much resources of the db. To avoid that a new config param
'report_sql_max_statement_time_seconds' was created, which sets the maximum time a report statement may
take to execute in seconds. If it takes longer it will be aborted.

reflowed the code, so that is is executed after is_sql_valid, and put
the code in a subroutine

Test plan:
0. if you run this test in ktd you should check if you are running
   mysql or mariadb, and then be able to switch database
1. create a new report by going to
More > Reports > New SQL Report
(/cgi-bin/koha/reports/guided_reports.pl?op=add_form_sql)

enter the following SQL (which only purpose is to run long)

select ExtractValue(bm1.metadata,'//controlfield[@tag="001"]')
  from biblio_metadata bm1
  join biblio_metadata bm2 on
    ExtractValue(bm1.metadata,'//controlfield[@tag="001"]') =
    ExtractValue(bm2.metadata,'//datafield[@tag="773"]/subfield[@code="w"]')

give the Report a name eg "extract_long"
2. run the report
3. report should be executed and return several 1000 lines on ktd
4. apply patch
5. add a the parameter to koha-conf.xml to the <config> </config> part
<report_sql_max_statement_time_seconds>0.001</report_sql_max_statement_time_seconds>

OR use the following statemtn
sed -i '/<biblioserver>/a <report_sql_max_statement_time_seconds>0.001<\/report_sql_max_statement_time_seconds>' /etc/koha/sites/kohadev/koha-conf.xml
6. restart koha
    koha-plack --restart kohadev
7. go back to the report page and rerun report which now should lead to an error
The following error was encountered:
The database returned the following error:
Query execution was interrupted, maximum statement execution time exceeded
Please check the log for further details.
8. in koha-conf.xml set
<report_sql_max_statement_time_seconds>20</report_sql_max_statement_time_seconds>
9. restart koha
10. rerun query, which now should execute as before
11. stop ktd, and restart with the "other" database

DB_IMAGE=mysql:8.0 ktd up
DB_IMAGE=mariadb:10.5 ktd up

12. repeat from step 1 (omit applying the patch)
13. Sign off
14. Thx

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:33 -06:00
Kyle M Hall c6ef3094ca
Bug 41440: Fix bug revealed by unit test
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:32 -06:00
Kyle M Hall 07c37c10c4
Bug 41440: (QA follow-up) Add unit tests
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:32 -06:00
Nick Clemens 365770c396
Bug 41440: (QA follow-up) Handle undef "script"
This silences warnings about undefinved values in the cache key

This seems to come from some calls in _build_languages_arrayref where we might not
have all variants of a language - the call could be prevented, but I am not sure
what the effects would be, so I simply convert undef (null value) to "undef" (string)
and use that as the cache key

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:31 -06:00
Nick Clemens 694765da2f
Bug 41440: Add caching to language_get_description and get_rfc4646_from_iso639
This should have no visible effect, just internal caching

To test:
1 - Do a search, check the language facets
2 - Apply patch, restart all
3 - Do a search, check the language facets
4 - No change
5 - Sign off

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:31 -06:00
Laura_Escamilla 2e218dcb69
Bug 42165: Add search landmark role to OPAC masthead search form
Test plan:
1. Apply the patch
2. Load the OPAC main page (opac-main.pl)
3. Inspect the search form
   → Confirm role="search" is present
4. Use a screen reader (or browser accessibility tree)
   → Verify a "search" landmark is announced
5. Ensure only one search landmark is present on the page
6. Perform a catalog search
   → Confirm search functionality is unchanged

Signed-off-by: Laura Woodward <lwoodard@artsbma.org>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:30 -06:00
Lucas Gass 4201fdfe2c
Bug 17387: DBRev 25.12.00.045
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:30 -06:00
Nick Clemens 1084dbcf33
Bug 17387: (QA follow-up) Add nonce to remaining script tag
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:29 -06:00
Jacob O'Mara 5868f784c7
Bug 17387: (follow-up) move js to dedicated file as per coding guidelines
Also amend _ to __ for translatable strings

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:29 -06:00
Jacob O'Mara 65fa5cb43e
Bug 17387: (follow-up) make deleted_on column searchable
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:28 -06:00
Jacob O'Mara b6eeffb79f
Bug 17387: (follow-up) fix embed items on get deleted biblio
Add x-koha-embed parameter to GET /deleted/biblios/{biblio_id} and use
$c->objects->to_api() to properly handle embedding items.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:28 -06:00
Martin Renvoize 8681b48fdb
Bug 17387: (follow-up) move records_restore under editcatalogue permission
The restore functionality is a cataloguing operation and should live
under the editcatalogue permission module (bit 9) rather than tools
(bit 13), consistent with where it appears in the UI.

Update all references:
- userpermissions.sql and atomicupdate: module_bit 13 → 9
- Swagger x-koha-authorization: tools → editcatalogue
- CGI flagsrequired: tools → editcatalogue
- Templates: CAN_user_tools_records_restore → CAN_user_editcatalogue_records_restore
- C4::Auth::get_cataloguing_page_permissions: remove from tools
  sub-perms list (editcatalogue '*' already covers it)
- Tests: module_bit 13 → 9

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:27 -06:00
Martin Renvoize 06f4abbf9a
Bug 17387: (follow-up) address review feedback
- Fix permission description to explicitly mention bibliographic records
  and items (not just 'records')
- Fix date filter to use correct DB column per table: deletedbiblio uses
  'timestamp', deleteditems uses 'deleted_on'
- Remove leftover CAN_user_editcatalogue_edit_catalogue condition from
  the Batch editing section in cat-menu.inc; a user with only
  edit_catalogue would see an empty section heading
- Wrap biblio and item restore operations in DB transactions so that a
  failed store cannot leave a record inserted without being removed from
  the deleted table (or vice-versa)
- Catch Koha::Exceptions::Object::DuplicateID in the item restore
  controller and return a human-readable 409 instead of a generic 500
- When 'Restore bibliographic record' is clicked in the deleted-bib
  warning modal, fetch the biblio and open the full restore modal with
  the originally clicked item pre-selected, rather than restoring
  immediately without item selection

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:27 -06:00
Jacob O'Mara 3a83da117e
Bug 17387: (follow-up) restore tests should account for new library groups checks
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:26 -06:00
Jacob O'Mara c07639777d
Bug 17387: (follow-up) take library groups in to account for permissions check
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:26 -06:00
Jacob O'Mara b1df5271f3
Bug 17387: add logging for biblio/item restore
This adds RESTORE action for biblios and items to the action log

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:25 -06:00
Jacob O'Mara f4c154f155
Bug 17387: add restore records permission as child of cataloguing perms
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:25 -06:00
Jacob O'Mara f2396c5389
Bug 17387: add links for restore item/biblio to cataloguing
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:24 -06:00
Jacob O'Mara 356576f6db
Bug 17387: add sidebar date filtering for item/biblio restore
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:24 -06:00
Jacob O'Mara 61909daa10
Bug 17387: add modal for restoring biblios with item selection
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:23 -06:00
Jacob O'Mara 4c3cf66681
Bug 17387: add items embed to deleted biblios API
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:23 -06:00
Jacob O'Mara 2191941971
Bug 17387: add modal warning for items with deleted biblios
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:22 -06:00
Jacob O'Mara 4285de5deb
Bug 17387: add biblio embed to deleteditems return
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:22 -06:00
Jacob O'Mara 321e9647ea
Bug 17387: add pagination headers and support comma-delimited Accept headers
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:21 -06:00
Jacob O'Mara d2ad055c30
Bug 17387: add restore records page
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:21 -06:00
Jacob O'Mara 98d74ff6bc
Bug 17387: item cannot be restored if associated biblio does not exist
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:20 -06:00
Jacob O'Mara 1baf34b1c4
Bug 17387: reindex biblio/item on restore
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:20 -06:00
Jacob O'Mara 16637d8b4f
Bug 17387: add restore item/biblio tests
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:19 -06:00
Jacob O'Mara 3dd662a8d7
Bug 17387: update swagger for deleted biblios/items restore
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:19 -06:00
Jacob O'Mara 6272f438d5
Bug 17387: add deleted biblios restore api
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:18 -06:00
Jacob O'Mara d7ce637ed4
Bug 17387: add deleted items restore api
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:18 -06:00
Jacob O'Mara 291261678b
Bug 17387: add restore functionality to Old::Biblio/Item
TEST PLAN: Item/Biblio Undelete Feature

  SETUP:
  - Apply patches and update database
  - Give test user "Restore deleted records" permission under Cataloging
permissions
  - Delete at least one bibliographic record with items attached
  - Delete at least one standalone item

  TESTING:

  Navigate to Cataloging:
  - Go to Cataloging home page
  - Click "Restore deleted records" link (should be visible with
permission)

  Test Restore Records Page:
  - Verify two tables appear: "Deleted bibliographic records" and
"Deleted items"
  - Verify deleted records appear in tables with biblio ID, title,
barcode, deleted date

  Test Date Filtering:
  - In left sidebar, enter date range in "Deleted from" and "Deleted to"
fields
  - Click "Apply" button - tables should filter by date range
  - Click "Clear" button - filter should reset

  Test Item Restore:
  - Find a deleted item in the items table (with existing biblio)
  - Click "Restore" button in Actions column
  - Verify success message appears and item disappears from table
  - Search catalog for the restored item - confirm it's back

  Test Biblio Warning Modal:
  - Find a deleted item whose biblio is also deleted
  - Click "Restore" button on the item
  - Modal appears warning "bibliographic record is deleted"
  - Click "Restore biblio" - biblio restores, modal closes
  - Click "Restore" on the item again - item now restores successfully
  - Verify both records are back in catalog

  Test Biblio with Items Restore:
  - Find deleted biblio with multiple deleted items
  - Click "Restore" button for the biblio
  - Modal shows checkboxes for each deleted item
  - Select some items (not all), click "Restore with selected items"
  - Verify biblio and selected items restore
  - Unselected items remain in deleted items table

  Permission Testing:
  - Remove "Restore deleted records" permission from user
  - Verify "Restore deleted records" link no longer appears in
Cataloging home

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Sponsored-by: ByWater Solutions
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:17 -06:00
Laura_Escamilla e66cfb8dba
Bug 42076: Add a Vendor ID column within the serial search results for subscriptions.
To test:
1. Go to the Serials module and create or edit a subscription.
2. Search for a vendor to add. Note that only the vendor name and Select column are visible.
3. Apply the patch and refresh the vendor search result page.
4. Notice that the vendor ID column is now visible.
5. Sign off and have a great day! :D

Signed-off-by: Emmanuel Bétemps <e.betemps@gmail.com>
Signed-off-by: Angela Berrett <angela.berrett@familysearch.org>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:17 -06:00
Nick Clemens 6393701c2a
Bug 40972: (QA follow-up) POD
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:16 -06:00
Mark Hofstetter bb0d94b261
Bug 40972: Add XSLT record processor filter hook for plugins
Koha's XSLT display path now calls a plugin hook so plugins can
extend the record processor filter list before MARC is transformed.
This enables prototype punctuation filters (and similar) to run
as plugins without core changes.

Test plan:
1) Enable plugins in koha-conf.xml.
2) Install the TestPunctuation plugin from https://github.com/HKS3/koha-test-punctuation
3) Enable the plugin in Koha admin.
4) View a biblio in staff or OPAC with XSLT display enabled and confirm
   the filter runs (e.g., 245$a gets a trailing period if missing).

Signed-off-by: Alexander Wagner <alexander.wagner@desy.de>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:16 -06:00
Owen Leonard 61c84f9e6a
Bug 8088: (follow-up) Tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:15 -06:00
Hammat Wele 22a13ba009
Bug 8088: Png-images of covers lost transparency
Images of covers must preserve transparency of png-images. This is
useful for example for the covers of CD/DVD disks.

Plan test :
1. Apply the patch.
2. In 'Koha administration > System preferences', set
   'LocalCoverImages' to 'show' and  OPACLocalCoverImages to 'show'.
3. Create a new bibligraphical record
4. In the record details select tab Images and click on upload
5. Click on « Drop files here » and select the image file to upload then
   click «Process images»
6. Search for the record created on step 3
7. Click on the record to view detail page (detail.pl)
8. Click on the image to view it fully
   ---> Notice the background is transparent
9. Save the image and open it
   ---> Notice the background is transparent

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:15 -06:00
Baptiste 3af877e78a
Bug 39027: Order news on opac-main.pl by publication date instead of ID
On the OPAC, news should rather be ordered by the publication date than
by the creation date (~ id): a user want to see last published news
first and not last created news, regardless of their publication date

TEST PLAN:
1 - Create 3 news, have the 3 news published in an order different from
  their id (for example news 1 => 2020, news 2 => 2025, news 3 => 2023)
2 - Go on http://youropac/cgi-bin/koha/opac-main.pl -> news are ordered
  by ID
APPLY PATCH
3 - Repeat 2 -> news are ordered by Date

Signed-off-by: Mercury <mercury@coffee.geek.nz>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:14 -06:00
Thomas Klausner 1bb30a26cd
Bug 40577: Bulk biblio ES index update after auth change
When merging (or updating) authorities, the biblios using the auths might also be updated. If this happens, Koha used to create one background job for each biblio. If an auth is used in a lot of biblios, this creates a lot of background jobs, each to index one single biblio.

This patch fixes that by collecting the affected biblios and putting all of them into one reindex background job.

Test plan:

* Apply the patch
* run the unit test t/db_dependent/Authority/Merge.t
* the test should now have 16 tests in total, with a new test #15 output like this:

    1..3
    ok 1 - 3 bibliographic record updated while merging two authorities
    ok 2 - we have only one more background job
    ok 3 - we have the correct biblio ids in the job to be indexed
ok 15 - merge with ModBiblio only creates one indexer background job for multiple biblios
ok 16 - no warnings

Manual test plan (rather cumbersome):

* Start KTD with ElasticSearch
* Create an PERSON authority (eg "100a Test")
* Create three biblios which use that authority in eg 100
* Access the DB to get the count of background jobs
  * `ktd --dbshell`
  * `select count(*) from background_jobs;`
  * the count should be the same as the number of biblios you created (eg 3). But whatever it is, remeber it
* Edit the authority (change 100 to "Test new")
  * check if the change is reflected in the linked biblios (100a = "Test new")
* Check the Background jobs in the DB again:
  * `select count(*) from background_jobs;`
  * should be three more than previously (eg 6)
  * You can use this SQL to verify that the last three jobs each contain one biblionumber (which is what we want to fix):
  * `select json_extract(data,'$.record_ids'),enqueued_on from background_jobs order by enqueued_on desc limit 3;`

+-----------------------------------+---------------------+
| json_extract(data,'$.record_ids') | enqueued_on         |
+-----------------------------------+---------------------+
| [2299]                            | 2026-03-25 13:19:34 |
| [2300]                            | 2026-03-25 13:19:34 |
| [2301]                            | 2026-03-25 13:19:34 |
+-----------------------------------+---------------------+

* Now apply the patch and run restart_all

* Edit the authority again, setting 100a to "Test new patched".
* The biblios should change accordingly
* Check the Background jobs in the DB again:
  * `select count(*) from background_jobs;`
  * This should now be only 1 more then the last time (so eg 7)
  * Check the details of the background jobs:
  * `select json_extract(data,'$.record_ids'),enqueued_on from background_jobs order by enqueued_on desc limit 3;`
  * The most recent entry should now contain three items in one row, and the following two should be the same as in the previous query (i.e. the old jobs from the edit before the patch)

+-----------------------------------+---------------------+
| json_extract(data,'$.record_ids') | enqueued_on         |
+-----------------------------------+---------------------+
| [2299, 2300, 2301]                | 2026-03-25 13:29:17 |
| [2300]                            | 2026-03-25 13:19:34 |
| [2299]                            | 2026-03-25 13:19:34 |
+-----------------------------------+---------------------+

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:14 -06:00
Janusz Kaczmarek 95267338d5
Bug 42016: Add identifier-other search field for authorities (MARC 21)
MARC 21 authority records often have 024 $a (Other Standard
Identifier) - https://www.loc.gov/marc/authority/ad024.html,
but the field 024 is missing from ES search fields and the record
cannot be found with these identifiers.

Test plan
=========
1. Have a ktd instance with Elasticsearch.
2. Add a 024 field to an authority record, i.e. for Alatriste, Gustavo:
   024 7# $a 86971927 $7 viaf
3. In Authorities, make an 'Entire record' search for the added
   identifier. You should get no results.
4. Apply the patch, reindex with:
   sudo koha-elasticsearch --rebuild -r -d -a kohadev
5. Repeat p. 3. You should now be able to find the record.

Sponsored-by: Pontificia Università di San Tommaso d'Aquino (Angelicum)
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 14:10:07 -06:00
Lucas Gass 98abc435c8
Bug 21820: DBRev 25.12.00.044
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:19 -06:00
Paul Derscheid 77d6fe1732
Bug 21820: (QA follow-up) Fix bug number in atomicupdate
- bug_number was set to 40853 instead of 21820 (probably copy-paste error)

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:19 -06:00
Thomas Klausner 55308aa365
Bug 21820: Add ElasticsearchEnableZebraQueue syspref to control Zebra indexing when using Elasticsearch
Previously, Koha always added items to the Zebra queue when indexing via Elasticsearch. This adds a new system preference ElasticsearchEnableZebraQueue that allows disabling this behavior.

Unit Test:

* run the unit test t/db_dependent/Koha/SearchEngine/Elasticsearch/Indexer.t
* there should be a new subtest like this:

    1..5
    ok 1 - We start with an empty zebra queue
    ok 2 - new entry in zebraqueue for auth
    ok 3 - new entry in zebraqueue for biblio
    ok 4 - no new entry in zebraqueue for auth
    ok 5 - no new entry in zebraqueue for biblio
ok 5 - syspref ElasticsearchEnableZebraQueue

Manual Test Plan:

* Start ktd with Elasticsearch enabled
* As there is no interface to the zebraqueue, you need to connect to the DB and use SQL directly
  * ktd --dbshell
  * `select count(*) from zebraqueue;`
  * remember that number
* Edit any biblio or auth
* Check the zebraqueue again via SQL
* There should be one more entry

* Now apply the patch
* Then got to Sysprefs and set ElasticsearchEnableZebraQueue to `Disable`

* Edit another biblio or auth
  * has to be a different one, because Koha will prevent the same ID to be added again to zebraqueue
* Again check  zebraqueue via SQL
* There should be *no* new entry

QA Note:

the qa tool reports
[FAIL] Koha/SearchEngine/Elasticsearch/Indexer.pm
   FAIL	 pod_coverage
		sub _get_record has no pod coverage.

But this patch does not touch this code, so I guess this fail is caused by a stricter qa tool

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:18 -06:00
Tomás Cohen Arazi 75eeedd735
Bug 22639: Add export mappings button to mappings page
This patch adds an 'Export mappings' button to the Elasticsearch
mappings configuration page. It triggers a YAML download of the
current search field mappings using the new API endpoint.

To test:
1. Apply this patch (and the previous API endpoint patch)
2. Navigate to Administration > Search engine configuration
=> SUCCESS: An 'Export mappings' button appears next to 'Reset mappings'
3. Click 'Export mappings'
=> SUCCESS: A 'search_mappings.yaml' file is downloaded
4. Verify the YAML file contains the expected mappings structure
=> SUCCESS: File contains search fields grouped by index
5. Sign off :-D

Signed-off-by: Laurence <laurence.rault@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:18 -06:00
Tomás Cohen Arazi caa52ddf91
Bug 22639: Add GET /config/search_mappings endpoint
This patch adds a new API endpoint to retrieve the search field
mappings configuration (search fields and their MARC mappings, grouped
by index).

The endpoint supports content negotiation:
- application/json (default)
- application/yaml (for export/import compatibility)

An optional 'type' query parameter allows filtering mappings by MARC
type (marc21 or unimarc).

Implementation notes:
- New controller: Koha::REST::V1::Config::SearchMappings
- Reuses Koha::SearchEngine::Elasticsearch::raw_elasticsearch_mappings
- Registers 'application/yaml' MIME type in Koha::REST::V1
- Permission: manage_search_engine_config

To test:
1. Apply this patch
2. Rebuild the API bundle:
   $ yarn api:bundle
3. Restart Plack
4. Run:
   $ prove t/db_dependent/api/v1/config_search_mappings.t
=> SUCCESS: Tests pass
5. Test manually:
   $ curl -u user:pass /api/v1/config/search_mappings
=> SUCCESS: Returns JSON with search field mappings
   $ curl -u user:pass -H 'Accept: application/yaml' \
     /api/v1/config/search_mappings
=> SUCCESS: Returns YAML output
   $ curl -u user:pass /api/v1/config/search_mappings?type=marc21
=> SUCCESS: Only marc21 mappings are returned
6. Sign off :-D

Signed-off-by: Laurence <laurence.rault@biblibre.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:17 -06:00
Robin Sheat 975f0c3508
Bug 36550: allow defaults to be defined for koha-elasticsearch
koha-elasticsearch only allows the parameters to be specified on the
command line, otherwise it'll fall back to values hard-coded in the
script. This allows default parameters to be defined in
/etc/default/koha-common when installed with the Debian packages.

It also copies the current defaults into the defaults file.

Test plan, comprehensive:
1. You will need a package setup with elasticsearch, and some records.
2. Build a package (or copy the files modified in this patch over ones
   you might already have installed)
3. Modify some ES_* values in /etc/default/koha-community to your
   liking.  'ES_VERBOSE' might be a good one.
4. Do an elasticsearch reindex and observe that your parameter defaults
   are respected.

Test plan, simple:
1. With a git checkout:
   * `sudo ln -s .../koha/debian/scripts/koha-functions.sh /usr/share/koha/bin/koha-functions.sh`
   * `sudo ln -s .../koha/debian/koha-common.default /etc/default/koha-common`
2. Modify `.../koha/debian/scripts/koha-elasticsearch` around line 200
   so that it prints out the variables, and see that it properly respects
   modifying the defaults and/or setting the command line arguments.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:17 -06:00
Thomas Klausner 45a9cd081d
Bug 39158: (QA follow-up) Fixing POD
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:16 -06:00
David Gustafsson 37aca2b36c
Bug 39158: Reduce code duplication in marc_records_to_documents
Refactor marc_record_to_documents to avoid code duplicaiton.

To test:
1) Verify tests in t/db_dependant/Koha/SearchEngine/Elasticsearch.t all
   pass
2) Apply patch
3) Veriy tests in t/db_dependant/Koha/SearchEngine/Elasticsearch.t
   still pass

Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:16 -06:00
Lucas Gass 3f5372717f
Bug 38136: DBRev 25.12.00.043
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:15 -06:00
Lucas Gass bf2cffdba6
Bug 38136: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:15 -06:00
Katrin Fischer 134497c28e
Bug 38136: Tidy
Feel free to squash.

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:14 -06:00
Julian Maurice e7f483def5
Bug 38136: Refactor database translations (alternative)
This has the same goals of bug 24975 but with a different approach.

Bug 24975 tried the "POT/PO files as database tables" approach but
it appeared that is not what best fits our needs.

Instead of a "source text / translated text" mapping, we need
translations to be attached to a specific "object" (or table row), which
has the added benefit of keeping translations unaffected when the
original string changes. It may or may not be what you want but at least
this avoids the problem where fixing an error in the original string
forces users to re-translate everything.

We also need to be able to translate all translatable properties of a
particular entity.

The solution proposed here is to add a DBIC component that will add
several DBIC relations to the `localization` table:
* a relation named `localizations` that will return all translations for
  all translatable properties for all languages.
* for each translatable property:
  * a relation named `{$property}_localizations` that will return all
    translations for a given property, for all languages
  * a relation named `{$property}_localization` that will return the
    translation for a given property, for the current language

To enable translations for a particular entity, we will need to:
* add two method calls in the "result class" corresponding to the
  localizable object type
* in the admin interface, add a link to the the translation popup.
  Unlike bug 24975 there is not a page where you can translate
  everything. I don't know if it is really useful (probably better to
  translate itemtypes when you are on the itemtype page). It can be
  added later if needed.

To give an example, this is what needs to be added manually in
Koha/Schema/Result/Itemtype.pm:

    __PACKAGE__->load_components('+Koha::DBIx::Class::Localization');
    __PACKAGE__->localization_add_relationships(
        'itemtype', # the PK column
        'description' # a list of translatable columns
    );

(see POD in Koha/Schema/Component/Localization.pm for more information)

Final notes:
* I wanted this patch to have as few changes as possible so I kept
  methods like `search_with_localization`, `children_with_localization`
  even if I believe they can be removed (replaced by search/children)
  Localizations do not need to be prefetched/joined unless you want to
  sort on them.
  For the same reason, $itemtype->unblessed now always return a
  `translated_description` key.
  This can be fixed in followup patches
* There is cache involved (see
  Koha::Schema::Component::Localization::localization).
  Localizations in cache are grouped by object type and language (keys
  look like 'KOHA:localization:Itemtype:en')
* By keeping a single table for all translations of all entities, we
  cannot have data integrity at the RDBMS level, but automatic deletion
  of translations is handled by the DBIC `cascade_delete` feature, so
  integrity should be fine as long as no raw SQL update/delete operation
  is involved.

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:14 -06:00
Nick Clemens 99eb693cd9
Bug 41733: Honor EmailPatronRegistrations in the API
This patch adds new overrides opac_reg_yes and opac_reg_no to the patrons api
and will send a notice to the library when a patron is registered via the API if the pref is
active and the NO override is not sent, or the YES override is sent

To test:
1 - Apply patches
2 - yarn build
3 - restart_all
4 - run unit tests

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:13 -06:00
Nick Clemens 8f6677be5a
Bug 41733: Unit tests
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:13 -06:00
Nick Clemens 925d5f50be
Bug 42107: Add search scores to staff results page
This patch ensures the scores from ES are passed through and added to the
search results in the staff side.

Initially hidden, the scores can be shown by entering the line below into the
JS console in developer tools in the browser, or added to IntranetUserJS to
make them always show:
$(".search_score").toggle();

To test:
1 - Have Koha using ES
2 - Do some searches, note results
3 - Apply patch, restart all
4 - Confirm results don't change
5 - Enter code from description into IntranetUserJS
6 - Do more searches
7 - Note the score is visible

To really note scores, you will need to add some weights to the ES config:
Set weights like:
title 32
author 16
subject 8

Signed-off-by: Andrew Auld <andrew.auld@openfifth.co.uk>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:12 -06:00
Jonathan Druart 6eed79ccf8
Bug 41994: Implement /sysprefs route to list system preferences
This new route will be used in a follow-up bug to auto-complete
sysprefs when using the header to search for sysprefs.

Note that this is based on top of bug 41834 that NULL options,
explanation and type in DB, but a future enhancement will populate the
response to return the values present in the yaml files.

Test plan:
1. Bundle the swagger specs and reload plack:
   yarn api:bundle && koha-plack --reload kohadev
2. Fetch the sysprefs using the new route:
 curl -u koha:koha --request GET 'http://${INSTANCE}-intra.localhost/api/v1/sysprefs' --header "Content-Type: application/json" | jq
3. Confirm that the tests pass
   prove t/db_dependent/api/v1/sysprefs.t

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 11:37:05 -06:00
Jonathan Druart f59c396024
Bug 42353: Store the node version in .nvmrc
The context is koha-i18n and the Weblate sync. We need to know which version of node to use before running yarn install.

Prior to the upgrade to Node 20 all our Koha versions were using the same version (18).

Now 22.11 is using 18 and others are using 20.

We need to announce which version is needed for each of our branch.

Test plan:
Apply this patch
Install nvm:
  curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
run `nvm use`
=> It tells you to install 20
nvm install 20
nvm use # will read .nvmrc and use 20
yarn install
=> Success

Checkout Koha's branch 22.11.x (git checkout origin/22.11.x)
Apply the patch for 22.11
nvm use
=> It tells you to install 18
nvm install 18
nvm use # wil read .nvmrc and use 18
yarn install => Success

git checkout bug_42353 (main + 42353's patch for main)
nvm use && yarn install
=> Success

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:53 -06:00
Lucas Gass 752fe3a04d
Bug 42300: Add title and aria-label to authlink on the OPAC detail page
To test:
1. Go to the OPAC detail page for a record with authority links.
2. Notice the magnifying glass icon is link but there is no text or aria label for screen readers
3. APPLY PAATCH, restart_all, clear browser cache
4. Repeat steps 1 -2.
5. You should see both an aria-label and a title labeled "View authority record"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:53 -06:00
Laura_Escamilla 6af5032c3a
Bug 41934: Add label to responsive control column header in OPAC course reserves
The responsive control column in the OPAC course reserves table
contained an empty <th>, which results in an empty header and
accessibility warnings from automated tools.

This patch adds visible text ("Expand") to the header cell to
provide a meaningful label for all users and resolve the issue.

To test:
1. Enable course reserves and add at least one course with items.
2. Visit the OPAC course reserves page.
3. Inspect the table header row.
4. Confirm the responsive control column now contains "Expand".
5. Verify no empty header warnings are reported by accessibility tools.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:52 -06:00
Baptiste Wojtkowski 6bb400bb9a
Bug 41997: Submit properly default suggester in suggestion.tt
Steps to reproduce, only on main.
1 - Open a form creation suggestion.
2 - With code explorer, notice that there is: <input type="hidden" id="suggestedby" name="suggestedby" value="" /> in the code
3 - Apply patch
4 - With code explorer, notice that there is: <input type="hidden" id="suggestedby" name="suggestedby" value="51" /> in the code
5 - Submit form, notice the suggester is properly written

Signed-off-by: Felicie <felicie.thiery@biblibre.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:52 -06:00
Petro Vashchuk b3cddc9994
Bug 41454: Remove unused dbh calls
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Amended-by: Jonathan Druart
Tidy C4/Search.pm

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:51 -06:00
Pedro Amorim 041fa90a0d
Bug 41861: Update how cost and price_paid show
1) Enabled ILLModule
2) Create a new ILL request, set a title, '42' for cardnumber and any library:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
3) (k-t-d required, cost is not editable in UI, but backends may set it) Run the following query:
koha-mysql kohadev
update illrequests set cost = 1;
4) Notice cost shows, now run the same but set to 0:
update illrequests set cost = 1;
5) Notice it doesn't show.
6) Apply patch. Refresh. Notice is shows '0' now.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Edith Speller <edith.speller@ukhsa.gov.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:50 -06:00
Eric Phetteplace f7327f6e45
Bug 42222: Use of uninitialized string in string eq in Auth.pm
Testing:
- Follow plack intranet and/or opac error logs, tail -f /var/log/koha/kohadev/plack-*-error.log
- Login using web browser
- Do a POST request on either intranet or OPAC, for instance:
  - Staff: Reports > create a new SQL report "select 'hello'"
  - Public: search, select a book, add to a list
- 'uninitialized string' error appears in respective logs
- Apply patch
- restart_all
- Do another POST request
- No further errors should appear in the logs

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:50 -06:00
Laura_Escamilla 36d757c952
Bug 41933: Add accessible label to course reserves search input
To test:
1. Apply the patch.
2. Navigate to the OPAC course reserves page:
   http://localhost:8080/cgi-bin/koha/opac-course-reserves.pl
3. Inspect the course reserves search input using browser developer
   tools.
4. Confirm the search input has an associated <label> containing
   accessible text (e.g. "Search courses").
5. Confirm the visual appearance of the search control is unchanged.
6. Sign off

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:49 -06:00
Lucas Gass 4de3130aec
Bug 41884: Adjust tests
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:49 -06:00
Lucas Gass 2186fa2798
Bug 41884: Make barcode a link to the item
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:48 -06:00
Lucas Gass c42f58d196
Bug 41884: Add better reporting when item fails to modify via batch mod
To test:
1. Search for the system preference, PreventWithDrawingItemsStatus
2. Select both in transit and checked out, then save
3. Find an item and put it in transit.
4. Try to batch modify the item, making it withdrawen.
5. View the details of job, you should see 1 item could not be modified.
6. APPLY PATCH, restart_all
7. Try the steps again, this time when you get to the details of the job you should see more information on the item that failed:

    1 item could not be modified.
        39999000001457 : Item is in transit

8. Repeat this steps with a a checkout out item.
9. Also try with multiple items that will fail, ensure the display looks good.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:48 -06:00
Nick Clemens 42b042b4a4
Bug 42312: Clear vendor address before saving new address lines
This patch clears the vendor address fields before processing the new input
and also adds a space between lines when joining the display

To test:
1 - Create or edit a vendor and enter 'Physical address' as
    Line 1
    Line 2
    Line 3
    Line 4
2 - Edit the vendor, clear physical address and enter:
    New Line 1
3 - Save, search for vendor, and note the address shows as
    New Line 1Line2Line3Line4
4 - Apply patch, yanr build, reload the page and clear cache
5 - Clear physicall address and enter
    Newnew line 1
6 - Search for vendor and confirm display is:
    Newnew line 1 Line 2 Line 3 Line 4

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:47 -06:00
Owen Leonard 2a1ccd9c4f
Bug 41835: (follow-up) Clean up CSS
There are some aspects of .subfield_line in addbiblio.css which are
incorrect or redundant. This follow-up removes those lines from
addbiblio.css and from the copied CSS in serials-edit.css

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:47 -06:00
Emmi Takkinen 6122e55a32
Bug 41835: Add CSS file for serial edits page
Add item forms Tag editor buttons on serial edits page were misaligned
with their respective input fields. This happens because there is no
rule for their parent div to display its children elements correctly.
This patch adds this rule for serial edits page in new serial-edits.css
file.

To test:
1. Make sure MARC framework for serials uses plugins in some 952 fields
   (e.g. dateaccessioned.pl plugin in field 952$d).
2. Find an active serial subscription and receive items for it.
3. Open Add item form.
   -> Note that Tag editor buttons aren't displayed inline with input
      fields.
4. Apply this patch, restart services if needed.
5. Refresh page.
   -> Note that Tag editor buttons are now displayed inline with input
      fields.

Sponsored-by: Koha-Suomi Oy
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-04-15 09:14:46 -06:00
Jan Kissig bcdd04c08b
Bug 42301: Fix error occuring on updatedatabase when using MySQL
This patch fixes an error when running updatedatabase on an instance using MySQL by defining a key length for an unique key. The unique key in table record_sources was introduced on 35380 but was lacking a key length which works in mariaDB but fails on MySQL.

Test plan:
a) run ktd with mysql f.e.: DB_IMAGE=mysql:8.0 ktd --proxy --name mysql8 up -d
b) enter ktd shell ktd --name mysql8 --shell
c) checkout a Koha version prior to 35380. f.e. "git checkout 67adbce6562fa352b143977747fa290f23c63fde"
d) run 'reset_all'
e) log in and check Koha version in http://mysql8-intra.localhost/cgi-bin/koha/about.pl which should show
25.12.00.036
f) git checkout main
g) restart_all
h) updatedatabase will throw the following error:
Upgrade to 25.12.00.037  [08:29:14]: Bug 35380 - Add new unique name to record sources, add default record sources, add is_system column to default record sources.
        ERROR: {UNKNOWN}: DBI Exception: DBD::mysql::db do failed: BLOB/TEXT column 'name' used in key specification without a key length at /kohadevbox/koha/C4/Installer.pm line 817
i) apply patch
j) 'updatedatabase' should run without errors now

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:46 -06:00
Jan Kissig d56976e9a6
Bug 42318: Populate record_sources with system defined values on install
This patch adds predefined values to the record_sources table on installation of a new Koha instance. The values are
- batchmod
- intranet
- batchimport
- z3950
- bulkmarcimport
- import_lexile
and were formerly hardcoded in marc-overlay-rules.tt. This was introduced in 35380 which took care of updated instances via a db_revs script but forgot installation scripts.

Testplan
a) start up a fresh ktd
b) login and check go to Administration -> Record sources
c) check that no source exists
d) apply patch
e) go to mysql shell 'koha-mysql kohadev'
f) empty the database to trigger Kohas installer: 'drop database koha_kohadev;' followed by a 'create database koha_kohadev;'
g) restart_all
h) open Koha in browser again, the web installer should appear now. Credentials are 'koha_kohadev' with pasword 'password'.
i) run through the installation and create an admin user,
j) login with that user and repeat b) and c)
k) check that record sources are now set with the values from the descriptioni above.

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-04-15 09:14:45 -06:00
Lucas Gass 5e44515ec3
Bug 35380: (follow-up) Fix DBRev 251200037.pl
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-15 09:14:39 -06:00
Lucas Gass c9c8ec5693
DBIC changes to Category.pm
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-14 16:11:42 -06:00
Jonathan Druart 6aa49457ed
Bug 41594: Add Cypress tests
* Add Cypress tests to cover the 404 redirect if the pref is disabled
* Add "invoices" as possible embed option for
  /acquisitions/vendors/:vendor_id
Seems relevant as the api def for invoice if expecting for it
api/v1/swagger/definitions/invoice.yaml
* Tell insertObject how to generate an invoice and return it.
Unfortunately we don't have a direct route for that, hence the embed
on /vendors/:vendor_id

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-14 10:49:05 -06:00
David Cook 4951426642
Bug 41594: Disable access to invoice-files.pl if AcqEnableFiles disabled
This patch makes it so that invoice-files.pl returns 404 if
AcqEnableFiles is disabled

Test plan:
0. Apply the patch
1. koha-plack --restart kohadev
2. Go to /cgi-bin/koha/acqui/invoice-files.pl?invoiceid=999
3. Note that you see a 404 page
4. Change "AcqEnableFiles" syspref to "Do"
5. Go to /cgi-bin/koha/acqui/invoice-files.pl?invoiceid=999
6. Note that you now see a "Files for invoice" page

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-14 10:49:05 -06:00
Jonathan Druart 54d710cfae
Bug 42048: Add a Cypress test
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-14 10:49:04 -06:00
David Cook 0c4cb8ce02
Bug 42048: Build patron search description more carefully
This patch builds the patron search description more carefully
to avoid XSS

Test plan:
0. Apply the patch
1. Log into Koha and click on "Patrons"
2. Change the "Category" and "Library" to any option but "Any"
   and click "Search"
3. Type "test" into the data table "Search:" box
4. Note some text should appear on the screen similar to this content
   and format:

   Patrons found for: Anywhere: test
   Library=Centerville, Category=Board

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-14 10:49:04 -06:00
David Cook 32e5c736e7
Bug 41261: Valid inputs to unapi
This change adds data validation to the "unapi" API endpoint.

Test plan:
0. Apply the patch
1. Go to http://localhost:8080/cgi-bin/koha/unapi
2. Note the 400 HTTP response code
3. Go to http://localhost:8080/cgi-bin/koha/unapi?id=bad
4. Note the 400 HTTP response code
5. Go to http://localhost:8080/cgi-bin/koha/unapi?id=koha:biblionumber:1&format=bad
6. Note the XML response listing the available formats for that ID
7. Go to http://localhost:8080//cgi-bin/koha/unapi?id=koha:biblionumber:1
8. Note the XML response listing the available formats for that ID
9. Go to http://localhost:8080//cgi-bin/koha/unapi?format=mods
10. Note the 400 HTTP response code
11. Go to http://localhost:8080//cgi-bin/koha/unapi?id=koha:biblionumber:1&format=mods
12. Note the MODS format returned for biblionumber 1

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-14 10:48:48 -06:00
Martin Renvoize 348846ceb0
Bug 40383: (follow-up) Add css files to gitignore
We need to add these generated files to the gitignore.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-04-13 14:56:34 +01:00
Martin Renvoize a738127ded
Bug 41216: (follow-up) Fix path to sample.tt in xgettext-tt2.t
Bug 41216 moved sample.tt into tt/en/ but the test still referenced
the old path, causing xgettext-tt2.t to fail.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-04-13 09:52:55 +01:00
Martin Renvoize bda5afd49c
Bug 40383: (follow-up) Fix flaky Cypress EDIFACT search tests in CI
Replace cy.wait(N) + bare assertion with assertion-based waits using
{ timeout: 10000 }, and replace .type(term, { delay: 50 }) with
.invoke('val', term).trigger('input') to avoid keyboard simulation
timing races against the 500ms debounce in the search component.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-04-13 09:28:07 +01:00
Janusz Kaczmarek 278aa32c03
Bug 41870: Warning "Use of uninitialized value $borrowernumber" in opac-detail.pl
Under some circumstances (OPACShowHoldQueueDetails set, hold made,
OPAC user not logged in) a warning like:

[WARN] Use of uninitialized value $borrowernumber in numeric eq (==) at /kohadevbox/koha/opac/opac-detail.pl line 598.

is being generated.

Test plan:
==========
1. Set OPACShowHoldQueueDetails to 'Show holds and their priority
   levels'.
2. From staff interface, place a hold on a record for any borrower.
3. Open a fresh browser (private mode or different browser), confirm
   that you are not logged in, go to this record.
4. Check the content of plack-opac-error.log. You should see a
   warning like:
   [WARN] Use of uninitialized value $borrowernumber in numeric eq (==) at /kohadevbox/koha/opac/opac-detail.pl line 598
5. Apply the patch ; restart_all.
6. Repeat p. 3 & 4. There should be no new warning now.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:13 -06:00
Janusz Kaczmarek 00c4c9a599
Bug 41863: Facets generated from Authorized values sometimes show empty labels
With facets generated from Authorized values, it may happen that the
list of authorized values lacks an entry for a code used in
a record. In this case, an empty entry will appear in the
facet list, with the number of uses but without any label.

It would be desirable to show at least the raw code, rather
than an empty label. This makes it easier to see which code has no
entry in the respective AV and to either add it or correct an
erroneous code.

Test plan:
==========

1. Have a standard KTD installation with Elasticsearch.
2. Add AV COUNTRY_CODES category and add two pairs (Authorized
   value, Description):
   * enk - England
   * xxu - United States

3. In the admin/searchengine/elasticsearch/mappings.yaml file,
   correct the definition of the pl search field as follows:

   pl:
-    label: pl
+    facet_order: 24
+    label: Publication place
     mandatory: ~
+    authorised_value_category: COUNTRY_CODES
     mappings:
-    - facet: ''
+    - facet: 1
       marc_field: 008_/15-17
       marc_type: marc21
       sort: 1

4. Reindex ES with:
   koha-elasticsearch --rebuild -r -d -b kohadev

5. Set the displayFacetCount systempreference to 'Show'
6. Perform a general search in the OPAC or staff interface: *
   View the 'Publication place' facet and notice a dozen empty
   entries, with only the number of occurrences.
7. Apply the patch ; restart_all
8. Repeat p. 6. You will see country codes that you can now add to AV
   or correct.

BTW, note that the tiny correction to Search.pm is analogous to
setting the label for 'special' types (i.e. itype, location, ccode,
holdingbranch, homebranch) two lines above.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:12 -06:00
Nick Clemens e83f14c5fe
Bug 41843: Gracefully handle XML errors during authority deletion
This patch updates move_to_deleted in Koha::Authorities to attempt to
handle encoding errors and delete a record with invalid marcxml.

If recoverable, the leader of the deletd record will be updated to
reflect deletion, but in the case it cannot the XML will be saved
unaltered.

To test:
1 - Find an authority record that is not used in your Koha
    More->Authorities, search for 'a'
    Record #1541 for example
2 - Make the record have errors:
    sudo koha-mysql kohadev
    UPDATE auth_header SET marcxml = UpdateXML(marcxml, '//datafield[@tag="040"]/subfield[@code="c"]',CONCAT('<subfield code',CHAR(27),'="c">OSt</subfield>')) WHERE auth_header.authid=1541;
3 - In the search results, click Actions->Delete
4 - Error 500!
5 - Apply patch, restart all
6 - Delete the record
7 - Success!

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

Amended-by: Jonathan Druart
tidy

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:12 -06:00
Jonathan Druart ebd8e4f169
Bug 42126: Make 00-strict.t run strict on all Perl files
The current implementation is wrong:
* we list explicitly the directories to process and it's not kept up-to-date (eg. booking and Koha are missing).
* we should process the files that are in the git repository, and use Koha::Devel::Files to list them.

This patch suggests to use git ls-files (through Koha::Devel::Files) and
process them one by one. Also only call strict_ok instead of
all_perl_files_ok.
all_perl_files_ok was doing strict_ok and syntax_ok, but syntax_ok is
doing a `perl -c` that is done in xt/pl_valid.t already

We can ignore xt and t: the tests are... run by the test suite.

On main:
Files=1, Tests=1360, 287 wallclock secs ( 0.05 usr  0.02 sys + 1677.26 cusr 268.46 csys = 1945.79 CPU)
real    4m46.741

all_perl_files_ok with t and xt:
Files=1, Tests=3488, 884 wallclock secs ( 0.10 usr  0.01 sys + 3820.09 cusr 720.63 csys = 4540.83 CPU)
real    14m44.086s

all_perl_files_ok without t and xt:
Files=1, Tests=2666, 586 wallclock secs ( 0.08 usr  0.03 sys + 2261.22 cusr 447.02 csys = 2708.35 CPU)
9m46.519s

With this patch (syntax_ok without t and xt):
Files=1, Tests=2663, 337 wallclock secs ( 0.09 usr  0.01 sys + 76.18 cusr 70.32 csys = 146.60 CPU)
real    5m36.870s

Note that Koha/Account/Credit.pm, Koha/Account/Debit.pm and Koha/Old/Hold.pm are failing strict_ok, we ignore them for now:
Inconsistent hierarchy during C3 merge of class 'Koha::Account::Credit':
        current merge results [
                Koha::Account::Credit,
        ]
        merging failed on 'Koha::Account::Line' at /usr/lib/x86_64-linux-gnu/perl-base/base.pm line 186.
BEGIN failed--compilation aborted at Koha/Account/Credit.pm line 22.

Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:11 -06:00
Victor Grousset/tuxayo 5fed11a14c
Bug 41216: (QA follow-up): fix path of xt/author/tt_valid.t in comment
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:11 -06:00
Jonathan Druart e8f473fb8f
Bug 41216: Adjust POD
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:10 -06:00
Jonathan Druart f088e300f8
Bug 41216: Remove confusion in test output
Clearly show the check's description in the test's failure

Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:10 -06:00
Jonathan Druart 2597cc8d2b
Bug 41216: Fix failing test
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:09 -06:00
Jonathan Druart c7f3976165
Bug 41216: Fix QA failures
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:08 -06:00
Jonathan Druart 1f1f89cc0a
Bug 41216: Correctly fix the translation using I18N
Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>

Amended-by: Add missing closing parenthesis
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:08 -06:00
Jonathan Druart 0f7b95d5a7
Bug 41216: Try to fix by adding a space between TT tag and attribute
Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:07 -06:00
Jonathan Druart 952933c31e
Bug 41216: Restore tt_valid.t
HTML1 is gone. Almost...
The previous rule said that we could not have TT tags within HTML tags
because it caused problems with the translation.
It was incorrect.
Bug 38720 removed that rule, and stated that the only problematic use
was a IF with ">".
It was also incorrect.

This one bug states that we cannot have TT tags to construct HTML tags
IF one of the attribute contains a translatable string.
The list of translatable attributes are listed in tmpl_process3.pl.

This patch restores xt/tt_valid.t (and move it to xt/author) and adjust
slightly the test to take into account this last statement.

Test plan:
0. Apply only this first patch
1. Run xt/author/tt_valid.t
=> It fails on mana-subscription-search-result.inc
2. Update po for your prefered language
=> The string "This resource has been reported more than" is not in the
po files

3. Apply the second patch, repeat 1 and 2
=> It still fails: adding a space between the TT tag and the attribute is not enough here
=> The string is picked now! but... install the template and open the
translated version =>

4. Apply the third patch, repeat 1 and 2
=> The tests pass!
=> The string is still picked, install => The template looks good!
Now if you are familiar with Mana you can try to see the translated
string on the UI

Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:07 -06:00
Emily Lamancusa bf7522b67c
Bug 41999: Search all data in suggestion column
Include all displayed fields in the DataTables-searchable data for the
Suggestion column of the suggestions table

To test:
1. Go to Acquisitions > suggestions
2. Create a new suggestion for your logged-in library. Be sure fill out
   all of the following fields:
   Title
   Author
   Copyright date
   ISBN or ISSN
   Publisher
   Publication place
   Collection title
   Document type
   Notes
3. Save the suggestion. Confirm that you see the new suggestion in the
   purchase suggestions table.
4. Using the "search" field above the table, search for a word in the
   title
--> Confirm that the purchase suggestion from step 2 is still shown
5. Clear the "search" field and search for the author's first or last
   name
--> Confirm that the suggestion from step 2 is still shown
6. Use the "search" field to search for each of the other pieces of
   information filled out in step 2
--> Confirm that the suggestion is still shown for each search

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:06 -06:00
Lucas Gass 872c256639
Bug 41977: (RM follow-up) Tidy commit
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:06 -06:00
Martin Renvoize dee69bb7bf
Bug 41977: Hold fee not charged for title-level holds when all items are on order
Koha's convention is that notforloan <= 0 means "holdable" and
notforloan > 0 means "not holdable". This is documented in both
IsAvailableForItemLevelRequest() and CheckReserves():

  "item with negative or zero notforloan value is holdable"

_calculate_title_hold_fee() was only searching for items with
notforloan = 0 or IS NULL, silently excluding items with negative
notforloan values (e.g. -1 = "Ordered"). When all items on a title
had a negative notforloan status, no items were found, @fees was
empty, and the method returned 0 — causing no fee to be charged.

Fix the filter to use <= 0 so it matches the same holdability rule
used consistently elsewhere in the codebase.

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Roger fredricks <roger.fredricks@westsussex.gov.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:05 -06:00
Pedro Amorim a4bbf98dbf
Bug 41849: Add tests
Test plan:
1) Enable DisplayAddHoldGroups sys prefs
2) Go to 'Henry' and click 'Search to hold':
<staff_url>/cgi-bin/koha/members/moremember.pl?borrowernumber=19
3) Search for 'test' and click the top 'Select all' link
4) Click the downward arrow in the 'Place hold' button and pick 'Place hold for Henry'
5) Set a pickup location for each hold and tick the 'Treat as hold group' checkbox
6) Click 'Place holds'.
7) Check-in any eligible barcode e.g.: 39999000005578
8) Click 'Confirm hold'.
9) Check-in that same barcode.
10) Notice the modal now shows 'Part of a hold group' with a link to the patron's holds table
11) Notice the modal now shows a 'Cancel all holds in hold group' button. Click the button. Confirm all holds are cancelled.

prove t/db_dependent/api/v1/patrons_hold_groups.t

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:05 -06:00
Pedro Amorim 15c63014ef
Bug 41849: Add 'Cancel all holds in this hold group' button
To the same filled holds check in modal

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:04 -06:00
Pedro Amorim 5ae5ac3330
Bug 41849: Add cancel hold_group REST API endpoint
Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:04 -06:00
Pedro Amorim 334337aa46
Bug 41849: Add hold_group info to the filled holds check in modal
This adds the same hold group info (with a link to the patron's holds)

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:03 -06:00
Pedro Amorim 61e5f97b31
Bug 41849: Preparation: DRY the hold_group info block for reusability
Added in bug 41957

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:03 -06:00
Pedro Amorim bd31a11131
Bug 41957: (QA follow-up) Add tt filter to link variable
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:02 -06:00
Pedro Amorim 55a934fc0d
Bug 41957: Show hold group information on 'Hold found' modal
Test plan:
1) Enable DisplayAddHoldGroups
2) Go to 'Henry' and click the 'Search to hold' button:
<staff_url>/cgi-bin/koha/members/moremember.pl?borrowernumber=19
3) Search for keyword 'test'. Click the 'Search' button.
4) On the search results page, click the 'Select all' option at the top
5) Click the downward arrow in the 'Place hold' toolbar button.
6) Pick 'Place hold for Henry Acevedo'
7) Tick the 'Treat as hold group' checkbox.
8) Set a pickup location for each of the holds.
9) Click 'Place holds'.
10) Check-in one eligible item (from one of the 3 records we just placed holds on) e.g.: 39999000001310
11) Notice the 'Hold found' modal now shows 'Part of a hold group' with a link directly to the patron's holds.

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:02 -06:00
Andrew Fuerste Henry 3490c1a90c
Bug 41953: (QA follow-up) Add html filter to TT variable
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:01 -06:00
Andrew Fuerste Henry b6bd98c0f9
Bug 41953: Add empty column value for ungrouped holds
Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:01 -06:00
Pedro Amorim acc2d6aa99
Bug 41953: Make it obvious that '(part of a hold group)' is expandable
Test plan:
1) Enable DisplayAddHoldGroups
2) Go to 'koha' patron and click the 'Search to hold' button:
<staff_url>/cgi-bin/koha/members/moremember.pl?borrowernumber=51
3) Search for keyword 'music'. Pick 2 results with available items by ticking the checkbox next to each record.
4) Click the downward arrow in the 'Place hold' toolbar button.
5) Pick 'Place hold for koha'
6) Tick the 'Treat as hold group' checkbox.
7) Set a pickup location for each of the holds. Click 'Place holds'.
8) Repeat steps 3 to 7, but pick 2 other records (creating a second hold group).
9) Log in to the OPAC as the 'koha' user:
<opac_url>/cgi-bin/koha/opac-user.pl
10) On the summary, click the 'Holds' tab.
11) Notice a new 'hold group' column is shown, similar to Staff UI
12) Notice the 'Part of a hold group' bit is now visually an expandable link, reflecting its behaviour

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:00 -06:00
Pedro Amorim 6f410c3797
Bug 41953: Add hold group column to OPAC holds table
Only displayed if DisplayAddHoldGroups is enabled

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:48:00 -06:00
Pedro Amorim e09b40ff1c
Bug 41878: Add tests
Run these and all other holds groups related tests:

prove t/db_dependent/Koha/Patron.t
prove t/db_dependent/api/v1/patrons_hold_groups.t
prove t/db_dependent/Koha/Hold.t
prove t/db_dependent/Reserves/HoldGroup.t

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:47:59 -06:00
Pedro Amorim 7dfc0db168
Bug 41878: Add Hold logging when grouping/ungrouping
Test plan:
-Enable DisplayAddHoldGroups and HoldsLog sys prefs
-Place 2 or more holds for a patron (regular, not grouped)
-Find the reserve_id for one of the holds, and view it in Tools > Logs. There will be an entry for the hold's creation.
-From the patron's account, select the holds and click 'Group selected'
-Run the log for the reserve_id, note that there is an entry generated by putting the hold into a group
-Suspend the hold, then view the log for that reserve_id. Note that the log entry for the suspension includes the hold_group_id.
-From the patron's hold tab, click on the number in the 'Hold group' column, then select 'Ungroup hold'
-Run the log for the reserve_id, and note that there is a new entry for ungrouping the hold

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:47:59 -06:00
Janusz Kaczmarek 7ccf568797
Bug 41859: Authority search autocomplete results not consistent with search results
When cataloging and using authority plugin, there is auto complete
feature. But hints it produces are not consistent with search
results made with the same operator. E.g. (with standard KTD test
data set) typing 'reusability' in 'Search main heading'/'Search
all headings'/'Search entire record' gives no hints but one result
("Computer software Reusability.")

It does not seem logical.

Test plan:
==========
1. Have a standard KTD installation.
2. Start cataloging. Open authority plugin for any controlled field
   (e.g. 100). Start typing 'Peter' (in any input field) with the
   intention to find a person with the first name Peter (observe
   that search type is set to 'contains'). You will get no hints,
   despite of the existence of some Peters in the authority file.
3. Press Search button. You will have a few results containing
   'Peter'.
4. This is not what we expect. We expect that the hints are consistent
   with the search results.
5. Apply the patch ; clear the browser cache.
6. Repeat 2. You should get the hints now.
7. Change the search type to 'starts with'. Start typing 'Peter' - no
   hints now. Start typing e.g. davi... - the are a few hints from
   surnames.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:47:58 -06:00
Owen Leonard f0b70d1af4
Bug 41760: Fix <tbody> and <tfoot> in several templates
This patch makes corrections to table markup in several templates,
fixing these issues:

- <tfoot> appearing before <tbody>
- <tfoot> appearing without a <tbody>

To test, apply the patch and view the following pages in the staff
client. In each case the table should look correct, including the
appearance of the table footer.

- Acquisitions -> Vendor -> Basket. Add orders to a basket if necessary,
    in order for the "Orders" table to appear.
- Acquisitions -> Vendor -> Basket -> Add to basket -> From existing
    orders (copy) -> Search for orders. You must have existing orders to
    populate the table of order search results.
- Acquisitions -> Vendor -> Receive shipments -> Receive a new shipment.
    You must have pending orders to populate the table. Receive at least
    one order to ensure data for the next step.
- Acquisitions home page -> "All available funds" table -> Click an
    amount linked in the "Spent" column.
- Administration -> Funds. You must have at least one budget and at
    least one fund under that budget.
- Administration -> Record overlay rules.
- Patrons -> View a patron record -> Accounting -> Transactions. If
    necessary you can use "Create manual invoice" to add a charge to the
    account.
    - From the list of transactions, click the "Pay" button next to one
      of them.
- Reports -> Cash register statistics wizard.
    - ??
- Reports -> Catalog by item type.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:47:58 -06:00
Lucas Gass 9539b21089
Bug 41267: (follow-up) Check if exists
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-10 09:47:51 -06:00
Lucas Gass 397c17e14c
Bug 42273: DBReb 25.12.00.042
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 13:27:53 -06:00
Emmi Takkinen 7ae9616df9
Bug 23415: (follow-up): Declare variable AllowFineOverrideRenewing
Missing variable causes JS error.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:38 -06:00
Jonathan Druart d0ba565dff
Bug 41568: (follow-up) Tidy kohaTable - circ/circulation_batch_checkouts.tt
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:38 -06:00
Jonathan Druart 08e559e207
Bug 41582: Fix tags - OPAC
TagsInputEnabled is not a syspref but a TT var defined at the top of the
template

Without this patch the "Add tag(s)" link on the opac-detail page is not
working

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:37 -06:00
Saiful Amin 7cdba6ba02
Bug 41383: SIP2 server does not search patrons by unique patron attributes (alternate IDs unusable in SIP2)
Currently, Koha::Patrons->find_by_identifier only searches for patrons by userid and cardnumber.

This patch extends this method to also search by unique borrower attributes (where unique_id=1).
This enhancement allows modules like SIP2 to identify patrons using alternative unique identifiers
defined in the system.

Test Plan:
1. Test current behaviour:
   - Go to Administration -> Patron attribute types
   - Create a new Patron attribute with:
      - Patron attribute type code = "AltID"
      - Description = "Alternate ID"
      - Unique identifier checkbox selected
      - Save
   - Edit any patron record and go to Additional attributes and identifiers
      - Add a value "altid123" to "Alternate ID"
      - Save
   - Use any SIP2 client to search for the patron using this attribute value. e.g.,
     `perl misc/sip_cli_emulator.pl -su term1 -sp term1 -l CPL --host localhost --patron altid123 -m patron_information`
   - Verify that the patron is NOT found with this message:
     'AFInvalid cardnumber'
     e.g.:
     READ: 64YYYY          00120260304    104611000000000000000000000000AOCPL|AAaltid123|AE|BLN|AFInvalid cardnumber|

2. Apply the patch.

3. Run the tests:
   prove t/db_dependent/Koha/Patrons.t
   - Verify that all tests pass, including the new cases for unique attribute lookup.

4. Test with patch applied:
   - Use any SIP2 client to search for the patron using this attribute value. e.g.,
     `perl misc/sip_cli_emulator.pl -su term1 -sp term1 -l CPL --host localhost --patron altid123 -m patron_information`
   - Verify that the patron is found with this message:
     'AFGreetings from Koha.'
     e.g.:
     READ: 64              00120260304    104328000000000000000000000000AOCPL|AA23529001000463|AEEdna Acosta|BLY|BHUSD|BV0|CC5|BD7896 Library Rd. Portland, OR 44240|BF(212) 555-1212|PB19800424|PCPT|PIY|AFGreetings from Koha. |

5. Test non-unique attribute:
   - Go to Administration -> Patron attribute types
   - Edit "Alternate ID" attribute type
      - Unique identifier checkbox NOT selected
      - Save
   - Use any SIP2 client to search for the patron using this attribute value. e.g.,
     `perl misc/sip_cli_emulator.pl -su term1 -sp term1 -l CPL --host localhost --patron altid123 -m patron_information`
   - Verify that the patron is NOT found with this message:
     'AFInvalid cardnumber'
     e.g.:
     READ: 64YYYY          00120260304    104611000000000000000000000000AOCPL|AAaltid123|AE|BLN|AFInvalid cardnumber|

6. Sign-off

Signed-off-by: Syed Faheemuddin <faheem@nexuslib.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:37 -06:00
Tadeusz „tadzik” Sośnierz 0bcf87df38
Bug 42302: Fix xgettext.pl's STDOUT output
xgettext.pl tries to produce output to STDOUT by default, but fails
because of an error in the code. It should go to the actual STDOUT
instead of a file named '&STDOUT'.

Test plan:
1. Run xgettext.pl:
     echo koha-tmpl/opac-tmpl/bootstrap/en/includes/serial-status.inc > transfiles
     ./misc/translator/xgettext.pl -vaf transfiles
2. Observe no standard output, and the existence of a file
   called `&STDOUT`. Output to the screen is:
     echo koha-tmpl/opac-tmpl/bootstrap/en/includes/serial-status.inc > transfiles
     kohadev-koha@kohadevbox:koha(main)$ ./misc/translator/xgettext.pl -vaf transfiles
     ./misc/translator/xgettext.pl: Outputting to STDOUT...
     ./misc/translator/xgettext.pl: Opening input file list "transfiles"
     ./misc/translator/xgettext.pl: Processing file "./koha-tmpl/opac-tmpl/bootstrap/en/includes/serial-status.inc"
3. Apply the patch.
4. Run again.
5. Observe that information is output to the screen (see example of
   outpunt in the bug comment).

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:36 -06:00
Lucas Gass d345453a23
Bug 41883: logaction when batch modifying holds
To test:
-APPLY PATCH and restart_all
-Place a hold
-Run the Holds log using the hold's reserve_id. There will be a single entry for the hold's creation.
-Find the hold using the batch hold modification tool, and update its pickup location
-Look at the action log, the chnage should be logged
-Try batch modding holds in other ways, ensure the changes are logged
-Turn off HoldsLog and batch modify some holds, the changes should NOT be logged.

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:36 -06:00
Tomás Cohen Arazi 894e144303
Bug 42273: Fix 'idenfity' typo in categories table
The categorycode column comment in the categories table had a typo:
'idenfity' instead of 'identify'. This fixes it in kohastructure.sql
and adds an atomic update to correct existing installations.

Test plan:
1. Apply this patch
2. Run:
   $ updatedatabase
=> SUCCESS: The atomic update is applied
3. Verify:
   echo "SHOW FULL COLUMNS FROM categories WHERE Field='categorycode';" | koha-mysql kohadev
=> SUCCESS: The comment reads 'unique primary key used to identify the patron category'
4. Sign off :-D

Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:35 -06:00
Lucas Gass ce0d0ec93a
Bug 42053: DBRev 25.12.00.041
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:35 -06:00
Kyle M Hall 38e2b4b991
Bug 42053: Add sip2 to userflags if it doesn't exist
Bug 37893 only creates the sip2 userflag if the instance has a
SIPConfig.xml set up. This userflag needs to be created reguardless
or the instance will not be able to use SIP in the future!

Test Plan:
1) Ensure you have no SIPConfig.xml
2) Run the bug 37893 update
3) Note the missing userflag
4) Apply this patch
5) Run updatedatabase.pl again
6) Note the userflag exists!

Signed-off-by: Fridolin Somers <fridolin.somers@biblibre.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:34 -06:00
Tomás Cohen Arazi 39352eabb1
Bug 41944: Fix template error for non-existent ILL requests
This patch fixes a template error that occurred when attempting to view
a non-existent ILL request in the staff interface. Previously, navigating
to ill-requests.pl with an invalid illrequest_id would result in a 500
error and a stacktrace.

The Perl-side fix (output_and_exit with 'unknown_ill_request') was already
applied. This patch adds the template-side guard: wrapping the ILL content
with [% UNLESS blocking_error %] to prevent processing ILL-specific content
when the request doesn't exist, following the same pattern used in
serials/subscription-detail.tt and members/moremember.tt.

Test plan:
1. Ensure KTD is launched with Rapido ILL plugin and bootstrapped:
   $ ktd --proxy --name rapido --single-plugin ~/git/koha-plugins/rapido-ill up -d
   $ ktd --shell
  k$ cd /kohadevbox/plugins/rapido-ill/scripts
  k$ perl bootstrap_rapido_testing.pl
  k$ restart_all
2. Log into the staff interface at http://rapido-intra.localhost.
3. Point your browser to a non-existent ILL request, e.g., http://rapido-intra.localhost/cgi-bin/koha/ill/ill-requests.pl?op=illview&illrequest_id=1000
=> SUCCESS: An 'ILL request not found.' message is displayed, consistent with other not-found pages in Koha.
4. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:34 -06:00
Jan Kissig 581c9b39bb
Bug 28297: Allow syspref text fields to be saved when content changed to original value
This fixes the behavior on changed system preferences in HTML input text fields. Preferences which are changed back to the original value can be saved now without reloading the system pref page.
This will only affect input fields with type=text as they have the property defaultValue which is checked to highlight a field as 'modified'.

Testplan:
a) take a syspref with an HTML text input field f.e. AccessControlAllowOrigin
b) write '*' into the iput field. Notice that the preference is marked 'modified'. Click 'save'
c) now got to the text field again and delete that '*'. Notice the preference does not get marked as modified and can theefore not be saved.
d) apply patch and reload page
e) repeat b) and c) and notice that the preference gets marked 'modified' on both cases.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:33 -06:00
Laura_Escamilla b4c3352f33
Bug 42142: Improve accessibility of login settings toggle button
Test plan:
1. Log in to the staff interface
2. Click on the logged in menu dropdown
3. View source or inspect the markup for the login settings toggle
   (gear button)
4. Confirm the icon element has aria-label="Toggle panel for login
   settings"
5. Confirm the interactive element does not expose aria-expanded
6. Paste the page source into the W3C Markup Validation Service
7. Confirm the validator reports that aria-label must not be specified
   on the i element

Apply the patch:
8. Repeat steps 1-3
9. Confirm the aria-label has been moved from the i element to the
   toggle link
10. Confirm the i element now uses aria-hidden="true"
11. Confirm the toggle exposes aria-expanded="false" before opening the
    panel
12. Activate the login settings toggle
13. Confirm the panel opens and aria-expanded changes to "true"
14. Activate the toggle again
15. Confirm the panel closes and aria-expanded changes back to "false"
16. Paste the updated page source into the W3C Markup Validation Service
17. Confirm the validator error is resolved
18. Sign off and have a great day!

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:33 -06:00
Jonathan Druart daa1b20312
Bug 41476: Fix plugins table when one of the plugin has errors
I've tried to install one of my old plugin and it exploded.

https://gitlab.com/joubu/koha-plugin-jump

Test plan:
Install a broken plugin and notice the Datatable error on the plugins
list
With this patch applied the error is gone

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:32 -06:00
Lucas Gass 7fd1ff8e89
Bug 41880: Add a unit test for item level holds logging
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:32 -06:00
Lucas Gass 27b76f9bf3
Bug 41880: Add a unit test
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:31 -06:00
Lucas Gass 8357a8f074
Bug 41880: Keep original copy of hold for proper logging
To test:
0. APPLY PATCH, restart_all.
1. Make sure HoldsLog is enabled
2. Place a record/bib level hold on bib A.
3. Move the hold to bib B.
4. Look at the diff column in the holds log. You should see the Old (0) biblionumber and the New (N) biblionumber. You should see nothing about itemnumber because this is a record level hold.
5. Ensure that the New and Old biblionumbers are the correct ones.
6. Place an item level hold on item A.
7. Mobe the hold to item B
8. Look at the diff column in the holds log. You should see the Old (0) biblionumber and the New (N) biblionumber. You should also see the New and Old itemnumbers since this is an item level hold.
8. Ensure that the New and Old biblionumbers AND the New and Old itemnumbers are correct.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:31 -06:00
Pedro Amorim a4903946ca
Bug 42238: Fix loading 'holds' tab when navigating with hash
I don't know what caused this to stop working. Possibly a change in the
markup due to a bootstrap upgrade?

Test plan:

Before applying patch:
1) Place any hold for Henry, link to help:
   <staff_url>/cgi-bin/koha/reserve/request.pl?biblionumber=76&findborrower=23529000035676
2) Open a new tab and visit the Patron details page directly on the
   'Holds' tab:
   <staff_url>/cgi-bin/koha/members/moremember.pl?borrowernumber=19#holds_panel
3) Notice it activates the correct tab, but doesn't load the table.
4) Open a new tab again, this time visit the Patron 'Check out' page
   directly on the 'Holds' tab:
   <staff_url>/cgi-bin/koha/circ/circulation.pl?borrowernumber=19#holds_panel
5) Notice the same behavior (tab activates, no table is loaded).
6) Apply patch. Repeat test plan. Confirm table loads on both
   occasions.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:30 -06:00
Jake Deery 5187bd52b9
Bug 42175: Fix Mojo under k.t.d
This patch fixes Mojo under k.t.d by providing CGI::Compile with an
absolute rather than relative path.

TO TEST:
== APPLY PATCH ==
a)  run reset_all
b)  run apachectl stop (if apache does not stop, may need to `sudo kill -9 apache2`)
c)  run /kohadevbox/koha/bin/opac prefork -m production -p -l http://*:8080 &
c)  run /kohadevbox/koha/bin/intranet prefork -m production -p -l http://*:8081 &
d)  confirm you can use k.t.d. as normal
== SIGN OFF ==

Signed-off-by: Eric Phetteplace <ephetteplace@cca.edu>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:30 -06:00
Jonathan Druart 1b1c23bc07
Bug 42200: Remove pl code
The code in the controller is not longer used. We should also restore
the default value (120)

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:29 -06:00
Lucas Gass 38bf53404e
Bug 42200: Convert milliseconds to seconds for SelfCheckInTimeout
To test:

1. Set, or leave, the SelfCheckTimeout at 120 seconds
2. Log in to SCO module
3. You will be logged out in 120 milliseconds
4. APPLY PATCH, clear browser cache
5. SelfCheckTimeout should work in seconds again.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:29 -06:00
Lucas Gass f6addef698
Bug 42182: Fix branch limitation on reports-home.tt
1- Create a StaffReportsHome HTML customization
2- Limit it by branch
3- Log into that branch and to to the reports home page
4- No HTML customization
5. APPLY PATCH
6. Try again, this time you should see the HTML customization.

Signed-off-by: Juliet Heltibridle <jheltibridle@rcplib.org>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:28 -06:00
Maryse Simard 61cffdc9f3
Bug 41783: Encode query parameters for suggestions filtering
When filtering by bibliographic information in the sidebar of the
suggestions management page, the '%' contained in the query is
not encoded. This can lead to errors if the search terms can be
evaluated as an encoded character.

Test plan:
    1. Go to Acquisitions > Suggestions
    2. Create a suggestion with a title like "200 recipes"
    3. In "Filter by" > Bibliographic information, search for "200"
    4. No results are shown in the table
    5. Apply the patch
    6. Search again (step 3). The suggestion should appear in the list.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:28 -06:00
Andrew Fuerste Henry 75b7cc4a95
Bug 42062: correct permissions check in template file
To test:
1 - have a biblio with circulation history
2 - confirm syspref intranetreadinghistory is enabled and you're logged in as a user with permissions view_checkout_history and circulate_remaining_permissions (or superlibrarian or top-level borrowers and circulation permissions)
3 - load the checkout history page on your biblio record
4 - confirm there is no Patron column and that column is not hidden via column config
5 - apply patch, restart services, reload page
6 - confirm Patron column is present

Signed-off-by: Fridolin Somers <fridolin.somers@biblibre.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:27 -06:00
Roman Dolny 656da5cb4b
Bug 41938: Argument "" isn't numeric in numeric gt (>) ... warnings in circulation.tt
The warnings:

[WARN] Argument "" isn't numeric in numeric gt (>) at
/kohadevbox/koha/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
 line 1072.
[WARN] Argument "" isn't numeric in numeric gt (>) at
/kohadevbox/koha/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt
 line 1188.

appears in the plack-intranet-error.log.

Test plan:
==========
1. Use a fresh KTD, observe plack-intranet-error.log.
2. Find any patron and go to "Check out" tab.
==> Warnings should appear in plack-intranet-error.log
3. Apply the patch; restart_all.
4. Repeat 2. No warnings in plack-intranet-error.log

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:27 -06:00
Nick Clemens 2c253faf2d
Bug 41267: (follow-up) Add more clarifying text
Signed-off-by: Andrew Auld <andrew.auld@openfifth.co.uk>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:26 -06:00
Nick Clemens 876869544f
Bug 41267: Add fill_other_biblio_holds_policy circulation rule
This patch adds a new rule for itemtypes to allow/prevent filling other holds on a bibliographic record
The rule will obey ReservesControlBranch for determining the rules

To test:
 1 - Apply patches
 2 - Setup/choose 2 item types in Administration->Item types
 3 - In Administration -> Circulation and fine rules scroll to the bottom
 4 - Set one item type to "Fill other holds on record" = 'Yes' and the other to 'No'
 5 - Place a next available hold on the record
 6 - Checkout the 'Yes' item type to the patron
 7 - Their hold should fill
 8 - Check in the items
 9 - Place a next available hold on the record
10 - Checkout the 'No' itemtype to the patron
11 - Their hold should not be filled
12 - Check in the item and confirm the hold
13 - Checkout the itme to the patron, the hold fills
     NOTE: While seemingly counter intuitive, the rule controls whether a hold NOT tied to this item will fill
     Confirming the hold adds an itemnumber to the hold. In most cases, these items will not be holdable, so won't trigger at checkin
14 - Set a rule for the patrons branch to No and set system preference ReservesControlBranch to 'Patrons library'
15 - repeat 5&6
16 - The hold should not fill
17 - Change reserves control branch to items home library
18 - checkin /checkout the yes item type
19 - The hold should fill

Signed-off-by: Andrew Auld <andrew.auld@openfifth.co.uk>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:26 -06:00
Kevin Carnes ea4600476e
Bug 21941: Fix GROUP BY in circ/reserveratios.pl
When using strict_sql_modes, the Hold ratios page causes an Incorrect
GROUP BY error and returns an Error 500.

This patch removes uncessary columns from the database query and adds
the biblio columns to the GROUP BY statement, so they can be selected.
Since it's already grouped by biblionumber, it should not cause any
additional rows to be created.

Test plan:
1. Make sure that strict_sql_modes is enabled in the koha-conf.xml
   file.
2. Place at least one hold on a book.
3. Go to Circulation and select Hold ratios.
4. Observe that you get an Error 500.
5. Apply patch.
6. Repeat step 3.
7. Observe that you can view the page.
8. If the table is empty, change the ratio to 1.
9. Sign off.

Sponsored-by: Lund University Library
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:25 -06:00
Paul Derscheid 3a6a79a607
Bug 15792: Throttle patron renewal links to prevent double-clicks
- Replace plain <a> renewal links in patron_messages.inc with the
  reusable throttled-button component introduced in Bug 40896
- Covers all four renewal links: warndeparture and expired states,
  in both circulation and patron detail contexts
- Include throttledButton.js in circulation.tt and moremember.tt
- Add persist mode and done icon state to throttled-button component
  so the button stays disabled on bfcache restore with a check icon
  instead of a misleading spinner

Test plan:
1) Create or update a patron category with an enrollment fee and an
   enrollment period in months (e.g. 24 months)
2) Create a patron of that type and write off any initial enrollment
   fee
3) Set the patron's dateexpiry to the past so the account is expired
4) Go to /cgi-bin/koha/circ/circulation.pl and scan the patron
5) Observe the "Renew" link in the expiration warning now shows as a
   small button with a rotate icon
6) Click "Renew" and confirm the button becomes disabled with a
   spinner, preventing a second click
7) Verify the patron is renewed correctly with only one enrollment
   fee charged and the correct expiry date
8) Press the browser back button to return via bfcache
9) Verify the button remains disabled and shows a check icon instead
   of the spinner, indicating the action was already completed
10) Repeat from the patron detail page (moremember.pl) to verify the
    same throttle and done-state behavior there
11) Test the warndeparture case by setting dateexpiry to a near-future
    date and confirming the same behavior

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:25 -06:00
Owen Leonard 96d2287a6a
Bug 42017: Fix content type of OPAC news RSS
This patch updates opac-news-rss.pl so that it outputs data with the
correct content type, text/xml.

To reproduce you must have at least one news item in your system. From
the OPAC main page, click the RSS feed link.
  - If you are using Firefox or Chrome you will see unstyled plain test
    on the screen.
You may or may not be able to see the problem if your
browser (e.g. Vivaldi) has a built-in RSS reader.

To test, apply the patch and restart services.

- In the OPAC, from the main page, click the RSS feed link at the bottom
  of the OPAC news list.
- You should see the XML view of the feed. This confirms that the
  browser is receiving the correct content type.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-09 08:27:17 -06:00
Martin Renvoize c513dee502
Bug 41368: Fix ManageMarcImport Cypress test timing issue
Replace the no-op `#job_callback` existence check with a recursive
`cy.wait('@jobPoll')` that loops through each `/api/v1/jobs/*` poll
response (arriving every ~1s) until it sees `status: finished`. This
avoids any fixed timeout — the test proceeds as soon as the API signals
completion, however long that takes on a given machine.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-04-01 09:53:45 -06:00
Martin Renvoize 92b1c11e3c
Bug 40383: (follow-up) Fix insertSampleEdifactMessages for new schema
We moved host/username/password out of vendor_edi_accounts into a
separate file_transports table between PQA and Pushed. Update the
Cypress test fixture to insert a file_transports record first and
reference it via file_transport_id, and clean it up in
deleteSampleEdifactMessages.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-27 16:29:16 +01:00
Martin Renvoize d35f76316d
Bug 41980: (follow-up) Fix Cypress test ambiguous field code search
Typing "CE" in the allow_fields dropdown now matches both
"CE - Unused" and "AY - Sequence number" (case-insensitive
match on "ce" in "Sequence"), causing the wrong option to be
selected. Use "CE - " to uniquely target the intended option.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-27 16:29:07 +01:00
Martin Renvoize 35a1757c97
Bug 40383: (RM follow-up) Use csrf_token over csrfToken
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-27 11:15:06 +01:00
Martin Renvoize 679afb31ec
Bug 35380: (RM follow-up) Move TT to own script tag
Adhere to JS19

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-27 11:03:06 +01:00
Martin Renvoize 320e08a715
Bug 41462: (RM follow-up) Add koha_objects_class
Add the missing plural class mapper to the OAI Sets schema file

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-27 10:44:28 +01:00
Brendan Lawlor b3163a009b
Bug 41638: (QA follow-up) Make label for alt contact city consistent with main and alt address city
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:30 +01:00
Owen Leonard ecbaf3b4d5
Bug 41638: (follow-up) Fix more instances, some markup errors
I've added changes for member-alt-address-style.inc,
member-alt-contact-style.inc, and member-main-address-style.inc.

I've also made some related markup corrections: Missing ID on some
inputs, incorrect "for" label on some labels. These fixes don't correct
*all* validation errors but it's an improvement.

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:30 +01:00
Lisette Scheer d8a2496888
Bug 41638: Add IDs to labels on patron edit
When targeting with jquery and css, labels on the patron edit screen
can be fragile and complicated.
This patch adds ids to all the labels that follow the following
convention:

<label for="surname" class="required" id="surname_label">

Each id is the "for" with _label appended.

To Test:
1. Before applying the patch, go to add a new patron
2. Right click on the page and use the browser inspector/inspect
   element.
3. Observe there are no ids for the field labels.
4. Apply patch
5. Reload the page
6. Observe there are now ids for the fields.
7. Go through all the fields and confirm there is an id and that they
   follow the convention above.
8. Change mandatory fields and confirm the labels are still correct.
9. Edit an existing patron and confirm the ids are still present.
10. Sign off!

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:29 +01:00
Martin Renvoize ed3f0dcab2
Bug 41980: (Follow-up) Improve SIP2 field code descriptions
- Use sentence case for all dropdown descriptions
- Add 'Unused' descriptions for AR, AW, AX, BA, BB, BC, BJ, CE
- Move SIP field codes outside translatable strings to avoid
  untranslatable codes being sent to translators
- Update AE/AV/DA tooltip text: use 'Template Toolkit' (not
  'Template::Toolkit'), imperative form, and clearer object
  field descriptions

Signed-off-by: John Vinke <jvinke@austinseminary.edu>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:29 +01:00
Martin Renvoize 780ae2d3e3
Bug 41980: Add more descriptive labels to SIP2 field codes
- Add field name labels to all entries in getSIPFields() used by the
  Allow/Hide fields dropdowns (e.g. "CV - Alert Type" instead of "CV")
  sourced from C4::SIP::Sip::Constants field identifier names
- Add tooltips to ae_field_template, av_field_template and
  da_field_template explaining what each SIP field represents and
  which Template::Toolkit objects are available
- Improve ct_always_send tooltip to explain CT is Destination Location
- Improve cv_send_00_on_success tooltip to explain CV alert type codes
- Improve cv_triggers_alert tooltip to clarify the two alert behaviours
- Improve cr_item_field tooltip to explain CR is Collection Code

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: John Vinke <jvinke@austinseminary.edu>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:29 +01:00
Martin Renvoize 560dd35740
Bug 40841: DBRev 25.12.00.040
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:28 +01:00
Tomás Cohen Arazi 3c87468669
Bug 40841: Add limits configuration UI
This patch adds branch limits selection to the Z30.50/SRU configuration
page.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:28 +01:00
Tomás Cohen Arazi d6aae094b4
Bug 40841: Implement library limits on Z39.50 searches
This patchset implements Z39.50/SRU limits by branch in Koha.

Key implementation notes:
- Follows Koha's established pattern for library limits
- A new table linking to library limits is added
- The admin UI is adjusted so the form includes library limit selection
- Z39.50/SRU search controllers use the `search_with_library_limits`
  methods to correctly constraint the offered servers.

To test:
1. Apply this patches
2. Run:
   $ ktd --shell
  k$ updatedatabase
=> SUCCESS: Database upgrade goes well
3. Re-run:
  k$ updatedatabase
=> SUCCESS: No explosions, DB update idempotent
4. Restart everything:
  k$ restart_all
5. POint your browser to the admin page:
   http://kohadev-intra.localhost/cgi-bin/koha/admin/z3950servers.pl
=> SUCCESS: A new column is displayed for library limits brief
6. Edit `LIBRARY OF CONGRESS`
=> SUCCESS: The form includes the usual library limit selection UI
7. Choose some branches, excluding your current one (KTD's default is
   'Centerville').
8. Save
=> SUCCESS: It is saved correctly
=> SUCCESS: The table shows the edited library has the library limits
set
9. Edit again
=> SUCCESS: The selected limits are preserved when editing
10. Pick some authorities Z39.50 target, and exclude your current branch
    for testing purposes.
11. Go to Cataloguing, and choose 'New from Z39.50/SRU'
=> SUCCESS: Limits are respected!
12. Go to Authorities, and choose 'New from Z39.50/SRU'
=> SUCCESS: Limits are respected!
13. Same as 11 but with the advanced editor
=> SUCCESS: Limits are respected!
14. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:28 +01:00
Tomás Cohen Arazi e389129d61
Bug 40841: DBIC schema [DO NOT PUSH]
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:27 +01:00
Tomás Cohen Arazi 1abd451c5d
Bug 40841: Add Z39.50 library limits
This patch adds the library limits mixin to the Z39.50 classes following
a well established pattern in Koha.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:27 +01:00
Tomás Cohen Arazi 3169620db5
Bug 40841: DB changes
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:27 +01:00
Kyle M Hall 79ebdb9db7
Bug 41462: (QA follow-up) Add basic unit tests and koha_object_class methods
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:26 +01:00
Aleisha Amohia adcbd749e4
Bug 41462: Add Koha::OAI::Set(s) and Koha::OAI::Set::Biblio(s)
This architectural changes makes these tables available to the Koha namespace, and therefore the REST APIs.

To test: Confirm t/00-load.t tests pass and check for any syntax/tidy errors.

Sponsored-by: Auckland University of Technology
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:26 +01:00
Martin Renvoize da840f6c3f
Bug 40504: DBRev 25.12.00.039
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:26 +01:00
Martin Renvoize 16aa1edccd
Bug 40504: (QA follow-up) Fix QA script failures
- Add POD for _limit_counter in Koha::ILL::Request
- Add nonce attribute to <script> tag in select_manager.inc
- Collapse multi-line template tags in ill-requests.tt and suggestion.tt

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:25 +01:00
Lisette Scheer 6dfe1bf93a
Bug 40504: (QA follow-up) Tidy
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:25 +01:00
Jonathan Druart c3b6df654e
Bug 40504: Fix DB structure comparison
Fix misc4dev/run_tests.pl --run-db-compare-only

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:24 +01:00
Pedro Amorim f2bb65c4f9
Bug 40504: (QA follow-up): Prevent exiting without considering managed_by
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:24 +01:00
Pedro Amorim d997fc5981
Bug 40504: [DONT PUSH] dbic
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:24 +01:00
Pedro Amorim eaf50203b2
Bug 40504: (QA follow-up): Add 'managed by' filter
This adds a 'Managed by:' filter input on the left side filters.

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:23 +01:00
Pedro Amorim 2a9e51c9b2
Bug 40504: atomicupdate
Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:23 +01:00
Pedro Amorim 124a7bed65
Bug 40504: Add tests
Test plan, ktd, apply patches:
1) Enable ILLModule
2) Create a new ILL request, visit:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
3) Set a 'type', add a cardnumber e.g. '23529000035676', and a library. Click 'Submit'.
4) Notice the new 'Managed by' field entry on the 'Manage request' page. This should be assigned to your logged in user.
5) Click 'Edit request' from the top toolbar
6) Notice the new 'Managed by' form entry. Click 'Select manager' and click 'Search'. Only the 'koha' user should be returned.
7) Notice the message at the top 'Only staff with superlibrarian or ILL permissions are returned in the search results'.
8) Let's give 'ill' permissions to Edna Acosta, visit:
<staff_url>/cgi-bin/koha/members/member-flags.pl?member=5
9) Search for 'ill' permission (Search for 'interlibrary' on the top right search bar) and set 'ill' permission to Edna Acosta. Save.
10) Repeat 6). Notice that now when you use the patron search modal Edna Acosta is also returned. Select Edna. Click 'Submit' to edit the request.
11) Edit again. Notice the 'Managed by' is showing 'You', with the message 'Previously was Edna Acosta'. This is the desired behavior mimicked from Suggestions i.e. the staff member that is editing the request details is assumed to becomes it's manager by default. The staff member must click 'Keep existing manager' if they want to submit changes without changing the request's manager. Test buttons, save, verify that it all works.
12) Visit the ILL list table:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl
13) Click 'columns' and pick 'Managed by'. Notice it shows correctly with the link to the patron and sorting also works.
14) Create a new user without permissions (OPAC only user)
15) Access the OPAC ILL and login as that user (preferably on a different browser session as we'll want to access the staff UI as staff member again), visit:
<opac_url>/cgi-bin/koha/opac-illrequests.pl?op=add_form&backend=Standard
16) Pick a type and click 'Create'
17) Repeat 12) Notice the newly created request from OPAC is 'Managed by': Nobody. This is intentional as that request still needs to be assigned to a staff member.

Suggestions testing:
1) This testing is also important as the existing suggestions 'Managed by' functionality has been abstracted to be utilized by both the suggestions and ILL module.
2) Access suggestions, visit:
<staff_url>/cgi-bin/koha/suggestion/suggestion.pl
3) Click 'New purchase suggestion'. Notice the select manager button is also there. Click it and click 'search'. On the modal, the following message should show 'Only staff with superlibrarian or full suggestions permissions are returned in the search results' and the patrons shown should reflect that.
4) Do the same exercises here as we just did for the ILL module, i.e. create a staff member with permission 'suggestions.suggestions_manage' and ensure that shows on the modal, assign that patron as manager, edit again, verify that it defaults to 'You' and that the 'Keep existing manager' button works as before.
5) Finally, test the suggestions list table, repeat 2) and notice the 'Update manager' action at the bottom, test that that works just as well, by checking a few suggestions boxes, selecting a manager to your selection and clicking the 'Submit' button under it.

Run tests:
prove t/db_dependent/Koha/ILL/Request.t
prove t/db_dependent/api/v1/ill_users.t

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:23 +01:00
Pedro Amorim 11ac1c48ec
Bug 40504: Auto functionality:
- The staff member who creates a new ILL request is automatically assigned as its manager.
- No manager is assigned to ILL requests made by users without 'ill' permission e.g. patrons using the OPAC.
- When an ILL request without a manager has its status changed, the staff member making the change is automatically assigned as its manager e.g. by clicking 'confirm' or 'revert request'.
- It is possible to manually update a request's manager by clicking the 'Edit request' toolbar button when viewing a request. The patron search modal, 'select manager' button and all its functionality is shared with the 'select manager' present in the suggestions module. This means that when editing an ILL request, the 'managedby' defaults to the staff member editing the request, unless 'Keep existing manager' is clicked, like it happens in suggestions.
- This is fully core built, all backends will automatically hook into this functionality, because:
-- Automatic 'managedby' happen on status change, regardless of backend
-- All requests have access to 'edit request' page, regardless of backend
- Only if a logged in staff member exists does the auto set manager. If no staff member is found, nothing happens e.g. an ILL request status
was changed by a CLI command / cronjob

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:22 +01:00
Pedro Amorim e9c3c47bd5
Bug 40504: UI improvements
This turns the 'Select manager' and 'Restore manager' actions to proper buttons, well aligned in where they are displayed. The 'Select manager' functionality in the suggestions table page is also updated to make use of the same reusable button block

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:22 +01:00
Pedro Amorim ade2934e2a
Bug 40504: Add 'managedby' to illrequests
This also includes a 'manager' accessor which returns the Koha::Patron
object

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:22 +01:00
Pedro Amorim b0714a0b22
Bug 40504: Add ill/users API endpoint
Required to get users filtered by 'ill' permission for patron search modal

prove t/db_dependent/api/v1/ill_users.t

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:21 +01:00
Pedro Amorim 2c5bc2875c
Bug 40504: Preparation: Disambiguation of .clear_search
This is required, or clicking 'Clear' on the ILL patron search modal will trigger this event listener and cause a JS error, as the 'regular' ILL table is not shown/initialized when editing an ILL request

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:21 +01:00
Pedro Amorim afa20faf24
Bug 40504: Preparation: Add empty_table_settings option to patron_search_js
This is required for ILL 'select manager' functionality, as table_settings are already defined there and the user modal table will pick that up unless explicitly stated to not use it, thus empty_table_settings

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:21 +01:00
Pedro Amorim 09742f82c7
Bug 40504: Preparation: Abstract 'managed by' functionality from suggestions
Move 'managed by' and 'restore previous manager' form and show
functionality to reusable .inc files

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
2026-03-26 14:21:20 +01:00
Martin Renvoize 59890fa4da
Bug 40383: (follow-up) Add nonce attribute to inline script in basket.tt
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:20 +01:00
Martin Renvoize 631498e94e
Bug 40383: (follow-up) Address code review concerns
- Move TT interface/theme variables into a separate <script> tag to avoid
  mixing template variables with external JavaScript file scope
- Replace local escapeHtml() functions in edifact_interchange.js and
  edifact_errors.js with String.prototype.escapeHtml already defined
  in staff-global.js

Sponsored-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:20 +01:00
Martin Renvoize e9045ddedc
Bug 40383: (QA follow-up) Update Cypress tests to follow current best practices
Remove before()/after() DB hooks — all assertions already use cy.intercept
mocks so no real database messages are needed. Extract repeated button
injection into an openEDIModal() helper. Replace all cy.wait(N) fixed-time
waits with assertion-based waits and cy.wait('@alias') for network sync.
Fix a Bootstrap 5 race condition in the modal-close cleanup test where
hidden.bs.modal fired after the backdrop animation, causing the re-opened
modal's focus class to be removed mid-test.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:19 +01:00
Martin Renvoize 4da37cad60
Bug 40383: Add Cypress test coverage for EDIFACT modal
This patch adds end-to-end testing for the EDIFACT modal interface,
providing coverage of all user interactions and functionality.

**Test Coverage:**
- Modal display and initialization with proper Bootstrap navbar structure
- View toggle functionality between tree and raw display modes
- Search functionality including navigation, result counting, and highlighting
- Focus functionality for basket and invoice contexts
- Error handling for invalid message IDs and network failures
- Accessibility features including keyboard navigation
- Responsive behavior and modal interaction patterns

**Test Plan:**
1. Apply patch
2. Ensure KTD environment is running with test data
3. Run tests: `npx cypress run --spec "t/cypress/integration/Acquisitions/Edifact*"`
4. **Verify Test Results:**
5. **Manual Verification:**
   - All test scenarios reflect real user workflows
   - Test data requirements are clearly documented in spec file
   - Test data is cleaned up after the tests are run

Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:19 +01:00
Martin Renvoize d726a93518
Bug 40383: Enhanced EDIFACT message modal with search and focus functionality
This patch completely rebuilds the EDIFACT message modal interface, featuring:

**Core Features:**
- Modern Bootstrap 5 navbar with integrated search functionality
- Tree view and raw view display modes for message structure
- Real-time search with regex support and result navigation
- Smart focus functionality that highlights relevant segments based on
  context (basket, invoice)

**User Interface:**
- Bootstrap navbar between modal header and body
- Search form with previous/next navigation and live result counts
- Clean separation between always-visible controls and view-specific toolbars
- Accessibility conscious with proper ARIA labels and semantic markup
- Streamlined DOM structure with direct modal-body scrolling

**Test Plan:**
1. Apply patch and run: yarn css:build to compile SCSS
2. Navigate to Acquisitions > EDIFACT messages
3. Click "View message" on any EDIFACT message
4. **Modal Display Testing:**
   - Verify modal opens with Bootstrap navbar between header and body
   - Confirm tree view is active by default with segment structure visible
   - Click raw view button to verify plain text display
   - Test modal responsiveness by resizing window
5. **Search Functionality Testing:**
   - Enter "UNH" in search field, verify navbar shows result count
   - Use previous/next buttons to navigate between results
   - Test search with terms like "ORDER", "NAD", "LIN" - all results should be found
   - Verify native browser clear button appears and works
   - Test case-insensitive search behavior
6. **Focus Functionality Testing:**
   - From basket page, click enhanced view link - should highlight relevant segments
   - From invoice page, click enhanced view - should focus on invoice-related segments
   - Verify focus highlighting persists during search operations
7. **Error Handling Testing:**
   - Test with invalid message ID, verify error display
   - Test network errors, confirm loading states work properly
8. **Accessibility Testing:**
   - Verify keyboard navigation works with search controls
   - Test screen reader compatibility with ARIA labels
   - Confirm modal can be closed with Escape key

Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:19 +01:00
Martin Renvoize 1f5f91e649
Bug 40383: Add JSON representation to Koha::Edifact and JSON download to edimsg.pl
This patch implements a JSON representation of EDIFACT interchanges for enhanced
programmatic access and debugging capabilities.

Backend Changes:
- Add to_json() method to Koha::Edifact module
- Structured JSON output with header, messages array, and trailer
- Segment parsing with raw data, elements arrays, and line_id tracking
- Line item grouping using LIN segment identifiers

Frontend Changes:
- Add JSON download button to edimsg.pl page
- Support for ?format=json parameter and Accept: application/json header
- Proper HTTP content-type handling for JSON responses
- Error handling for missing messages in JSON format

JSON Structure:
{
  "header": "UNB+UNOC:3+...",
  "messages": [
    {
      "header": "UNH+1+QUOTES:D:93A:UN:EAN008'",
      "segments": [
        {
          "tag": "LIN",
          "raw": "LIN+1+1+9780552150040:EN'",
          "elements": ["1", "1", "9780552150040:EN"],
          "line_id": "1"
        }
      ],
      "trailer": "UNT+25+1'"
    }
  ],
  "trailer": "UNZ+1+1'"
}

Test plan:
1. Apply this patch
2. Ensure you have EDIFACT messages in your system
3. Go to Acquisitions > EDIFACT messages
4. Click "View message" for any message
5. Click the new "JSON" button
6. Verify JSON downloads with proper structure
7. Verify direct URL access with ?format=json parameter works
8. Test with Accept: application/json header

Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:18 +01:00
Martin Renvoize e8cdc4e9d5
Bug 40383: Add unit tests for Koha::Edifact to_json method
This patch adds unit tests for the new to_json method being introduced
in Koha::Edifact module in the next patch.

The tests verify:
- JSON output generation and validity
- Proper structure with header, messages, and trailer fields
- Message array structure with correct number of messages
- Individual message structure (header, segments, trailer)
- Segment data integrity including raw data and elements arrays
- Line ID functionality for related segments (LIN, QTY, PRI, PIA)

Test plan:
1. Apply this patch and the next one
2. Run: prove t/Edifact.t
3. Verify all 63 tests pass
4. Confirm the new JSON-related tests (22 tests) execute successfully

Sponsored-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:18 +01:00
David Cook 11de56769b
Bug 42072: Improve message about deleting bib records
This patch clarifies which records will be deleted in
the batch item deletion (when no items remain).

Test plan:
0. Apply the patch
1. Go to "Reports" and click "Create from SQL"
2. Create a report with the following SQL:
select itemnumber from items limit 5
3. Run the report and then click "Batch operations with 5 visible records"
on the output page
4. Near the bottom of the screen note that there is a checkbox that
has a label reading "Delete bibliographic records if no items remain."
5. C'est tout!

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:18 +01:00
Janusz Kaczmarek 0dc06596e2
Bug 41759: The display of MARC 21 field 026 data (Fingerprint Identifier) is missing (both in intranet and OPAC)
Test plan
=========
1. Make field 026 active for Editor in MARC framework section (e.g. in default
   framework).
2. Edit any bibliographic record (with modified framework) adding 026 field data,
   e.g. 026 ## $a urdu esrt $b eses jecl $c 1790
3. Verify in Normal view (intranet) and in OPAC that you are unable to
   see the inserted data.
4. Apply the patch ; restart_all.
5. Verify in Normal view (intranet) and in OPAC that you are now able to
   see the fingerprint information from 026 field.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:17 +01:00
Janusz Kaczmarek 71af0f01d1
Bug 41758: Add Fingerprint Identifier data to Elasticsearch index mappings
Libraries cataloguing older monographic resources request fingerprint indexing.

In MARC 21, it would be the content of field 026, in UNIMARC - 012.

Test plan
=========
1. Start up KTD with Elasticsearch: ktd --search-engine es8 up -d
2. Make field 026 active for Editor in MARC framework section (e.g. in default
   framework).
3. Edit any bibliographic record (with modified framework) adding 026 field data,
   e.g. 026 ## $a urdu esrt $b eses jecl $c 1790
4. Verify that you are unable to find the record searching with fingerprint data.
5. Apply the patch ; restart_all.
6. Reindex with: koha-elasticsearch --rebuild -r -d kohadev
7. Now you should be able to find the record searching with fingerprint data
   (performing a general search or prefixed with 'fingerprint-identifier:').

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:17 +01:00
Owen Leonard 4cdce0479c
Bug 42134: String displays incorrectly: words “notices” and “for” appear concatenated
This patch changes the way the notices template checks for the existence
of the TranslateNotices system preference in order to avoid a missing
space in the corresponding label.

To test you should have at least two languages enabled under the
StaffInterfaceLanguages system preference.

- Apply the patch and go to Administration -> System preferences..
- Locate the TranslateNotices preference and set it to "Allow"
- Go to Tools -> Notices and edit a notice.
- The "Modify notice" page should have tabs each of your enabled
  languages as well as "Default."
- Each tab should have a "Format" section. Expand it and check that the
  label for the checkbox at the bottom is correct, e.g. "Apply format
  settings to all notices for this language."
- Return to system preferences and set TranslateNotices to "Don't
  allow."
- Reload the notice edit screen and check the "Format" section. The
  label should now read "Apply format settings to all notices."

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:17 +01:00
Martin Renvoize 3b75ede4fd
Bug 28884: DBRev 25.12.00.038
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:16 +01:00
Nick Clemens cc0e314f72
Bug 28884: (QA follow-up) Add some missing pod
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:16 +01:00
Janusz Kaczmarek 388bcec0ac
Bug 28884: Add 'ElasticsearchEscapeCharacters' and 'OpacElasticsearchEscapeCharacters' system preferences
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:15 +01:00
Janusz Kaczmarek 3b1566b402
Bug 28884: Unit tests
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:15 +01:00
Janusz Kaczmarek 3e5ba1e316
Bug 28884: ElasticSearch: Question mark in title search returns no results
Test plan:
==========
1. Have a ktd instance with Elasticsearch enabled.
2. In standard ktd installation there is a record with a title
   "Typical girls? : the story of the Slits." (with question mark
   attached to a word) - biblio # 229. We will try to find it.
3. Go to advanced search (OPAC or intranet) and try a *title* search
   with:
   Typical girls? the story of the Slits
   You should get no results.
   From the main page try a ccl-style search:
   ti:Typical girls? the story of the Slits
   You should get no results too.
4. Apply the patch ; updatedatabase ; restart_all.
5. Put ? in OpacElasticsearchEscapeCharacters and/or
   ElasticsearchEscapeCharacters system preference.
4. Repeat the searches from p. 3. You should get the record if a
   system preference was set for the given interface.

Sponsored-by: Pontificia Università di San Tommaso d'Aquino (Angelicum)
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:15 +01:00
Emily Lamancusa ccc5ca534c
Bug 42147: Update tests
To test: prove t/db_dependent/Reserves.t

Signed-off-by: Emmanuel Bétemps <e.betemps@gmail.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:14 +01:00
Emily Lamancusa 83e2c3888d
Bug 42147: Log all relevant hold data on hold creation
When logging a hold create action, include all hold fields that may be
defined at hold creation.

Fields that are not defined until later (such as found status, waiting
date, etc) are not included to avoid clutter.

To test:
1. System preference setup
    i.  Enable HoldsLog system preference
    ii. Set AllowHoldPolicyOverride to Allow
2. Place a hold in the staff interface
3. View the action logs entry for that hold creation
    i. Go to Tools > Log viewer
    ii. Select Modules: Holds and Action: Create
    iii. Click Submit
--> Note that the "info" column only shows the hold id
4. Apply patch and restart_all
5. Repeat steps 2-3
--> The "info" column should now show hold details

Signed-off-by: Emmanuel Bétemps <e.betemps@gmail.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:14 +01:00
David Nind 5861ecdd0e
Bug 41985: Fix wording on SIP2 account form: 'Syspref'
Fix spelling on the SIP2 account form to spell 'Syspref'
section heading in full ('System preference overrides').

Test plan:
1. Go to Administration > Patrons and circulation > Self-service
   circulation (SIP2) > Accounts > New account
2. Note the label for the section is "Syspref overrides".
3. Apply the patch.
4. Rebuild everything: yarn build
5. Hard refresh the page.
6. Note that "Syspref..." is now spelt in full as
   "System preference overrides".

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:14 +01:00
Paul Derscheid fa1fa49531
Bug 42149: (QA follow-up) Add aria-label to shortcut nav
- Add aria-label="Quick shortcuts" to the shortcut nav element
  in both the TT include and the Vue Help component
- Ensures all nav landmarks on the staff interface are
  uniquely labeled for screen readers (WCAG 2.4.1)

To test:
- Apply the patch
- Rebuild vue components with `yarn js:build`
- Open the staff interface home page
- View the page source and confirm the shortcut nav has
  aria-label="Quick shortcuts"
- Confirm that all three nav elements (Main, Breadcrumb,
  Quick shortcuts) have unique aria-labels

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:13 +01:00
Brendan Lawlor ac4b2127cc
Bug 42149: Add aria-label to main nav
The main <nav> should have a unique aria-label to differentiate it from the Breadcrumb nav

To test:
1. View page source and confirm there are two <nav> elements
2. Notice the main <nav> does not have a unique aria-label
3. Apply patch and check again
4. Confirm the main <nav> is labeled

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:13 +01:00
Brendan Lawlor 0fe44ec697
Bug 42143: (follow-up) Remove empty duplicate links from patron breadcrumbs
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:13 +01:00
Brendan Lawlor d7a1141e64
Bug 42143: Remove empty duplicate links from patron breadcrumbs
This patch imporves accessibility for screen readers by removing a duplicate empty link
that is rendered in the breadcrumbs on patron pages.
patron-title.inc already includes the links and doesn't need to be wrapped in <a> element.

To test:
1. Go to a patron record and tab through the breadcrumbs
2. Notice there is an empty link that is invisible but gets focus
   between the Patrons and <patron title> breadcrumbs.
3. Check the other tabs to confirm the issue is present
    Accounting, Routing list, Circulation history, Holds history, Notices,
    Statistics and Purchase suggestions
4. Click edit and check the breadcrumbs on that page
5. Enable TwoFactorAuthentication and check the manage two-factor auth page
6. Apply patch
7. Revist the pages from steps 1-5 and tab through the breadcrumbs again
8. Confirm there is no duplicate empty link

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 14:21:12 +01:00
Martin Renvoize 67158768e5
Bug 35380: DBRev 25.12.00.037
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 11:11:53 +01:00
Martin Renvoize 6aff5f361a
Bug 35380: (QA follow-up) Minor code quality fixes
- Add order_by => ['name'] to record_sources fetch in marc-overlay-rules.pl
  so filter dropdown options are consistently sorted
- Add COMMENT to record_sources.is_system column in kohastructure.sql
  to match convention of other columns in that table
- Remove commented-out Data::Dumper debug lines from biblios.t
- Remove redundant explicit object deletes inside subtests that are
  already wrapped in txn_rollback; the rollback undoes them anyway

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:50:53 +01:00
Martin Renvoize 38c7696866
Bug 35380: (QA follow-up) Fix data migration for pre-existing record sources
INSERT IGNORE silently skips rows whose name already exists, leaving
their is_system column at 0. Systems that already had 'intranet',
'z3950' etc. as record sources would not have them protected from
deletion. Added an explicit UPDATE after the INSERT to ensure all
known system record source names have is_system=1 regardless of
whether the row was just inserted or already existed.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:50:48 +01:00
Martin Renvoize 75d88eb36c
Bug 35380: (QA follow-up) Fix bulkmarkimport typo in marc-overlay-rules.tt
The CASE checks for 'bulkmarkimport' but the record source inserted by
the atomicupdate is named 'bulkmarcimport'. The mismatch caused the
display label and the JS filter option to fall through to the default
(untranslated) case. Fixed in both the rule filter display block and the
module_filter_options JS object.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:50:41 +01:00
Jan Kissig 9b7824d789
Bug 35380: (follow-up): Fix errors from qa script
Make atomicupdate script executable, add html filters to tt, annotate is_system as boolean (SQL12 coding guidelines)

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:50:09 +01:00
Jan Kissig 448eb4f69a
Bug 35380: Unit tests
updateBiblio API:
- tests for locked records (records with a given record_source which is set to can_be_edited = 0)
- tests for MARCOverlayRules bound to a record_source or fallback source (*)

As record_sources now has an is_system property which prevents it from getting deleted, all tests creating and deleting a record_source were changed so that is_system is set to 0 on creation.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:50:09 +01:00
Jan Kissig ce42c3b5ec
Bug 35380: Make updateBiblio-API respect Record overlay rules
This patch moves hardcoded source filter from 'MARC overlay rules' (batchmod, intranet, z3950, staged import, bulk import, import_lexile) to 'Record Sources' and makes them untouchable. They cannot be renamed or modified.
New record sources can be added and modified though.
All default and newly created record sources are available as filter for the module "Source" in 'MARC overlay rules'.
The API route for PUT /biblios/:biblio_id now checks for a given header x-record-source-id.
If an id is given the name of the rule is retrieved and forwarded as an overlay_context to ModBiblio.
If no id is given the fallback '*' is added as overlay_context.
Also the record source id is getting saved.

Test plan:
pre) run perl installer/data/mysql/updatedatabase.pl to add new is_system column
pre) run dbic to build Koha::Schema::Result::RecordSource as it gets a new columns is_system
pre) run 'redocly bundle --ext json api/v1/swagger/swagger.yaml --output api/v1/swagger/swagger_bundle.json' or 'yarn build'

a) enable system preference MARCOverlayRules and RESTBasicAuth
b) create new record source in /cgi-bin/koha/admin/record_sources and recognize its <NAME> and <ID>
c) create overlay rules in /cgi-bin/koha/admin/marc-overlay-rules.pl
f.e.
+----+-----+--------+----------+-----------+
| id | tag | module | filter   | preset    |
+----+-----+--------+----------+-----------+
|  1 | 245 | source | *        | protect   |
+----+-----+--------+----------+-----------+

This will make the 245 field protected from change, unless the record source matches
d) go to /cgi-bin/koha/catalogue/detail.pl?biblionumber=262 and change 245 (title)  via edit -> edit record
e) save and take a look the title is still the same
f) run the updateBiblio-Request from below with a API-testing client like REST Client
g) check that title gets overwritten even though it is protected. This is due the fact the updateBiblio is not setting an overlay context.

h) apply patch
redo d) and e)
i) add another overlay rule allowing your record source to change 245:
+----+-----+--------+----------+-----------+
| id | tag | module | filter   | preset    |
+----+-----+--------+----------+-----------+
|  1 | 245 | source | *        | protect   |
|  2 | 245 | source | <NAME>   | overwrite |
+----+-----+--------+----------+-----------+

j) run the updateBiblio-Request from below, be sure to fill out x-record-source-id:with your ID from step b)
k) check that title gets overwritten
l) run the updateBiblio-Request from below, but omit x-record-source-id or set it to another value than <ID>
m) check that title is not overwritten

PUT http://main-intra.localhost/api/v1/biblios/262
Authorization: Basic koha:koha
Authorization: Basic koha:koha
Content-Type: application/marcxml+xml
x-record-source-id: <ID>

<?xml version="1.0" encoding="UTF-8"?>
<record
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
    xmlns="http://www.loc.gov/MARC21/slim">

  <leader>01075cam a2200313 a 4500</leader>
  <controlfield tag="001">foo</controlfield>
  <controlfield tag="005">20200421093825.0</controlfield>
  <controlfield tag="008">120417s2012    ch a     b    001 0 eng  </controlfield>
  <datafield tag="040" ind1=" " ind2=" ">
    <subfield code="c">482</subfield>
  </datafield>
  <datafield tag="100" ind1="1" ind2=" ">
    <subfield code="a">Christiansen, Tom.</subfield>
    <subfield code="9">482</subfield>
  </datafield>
  <datafield tag="245" ind1="1" ind2="0">
    <subfield code="a">New Title</subfield>
    <subfield code="c">Tom Christiansen, Brian D. Foy &amp; Larry Wall.</subfield>
  </datafield>
  <datafield tag="260" ind1=" " ind2=" ">
    <subfield code="a">City</subfield>
  </datafield>
  <datafield tag="300" ind1=" " ind2=" ">
    <subfield code="a">Pages and more</subfield>
  </datafield>
  <datafield tag="942" ind1=" " ind2=" ">
    <subfield code="c">BKS</subfield>
  </datafield>
</record>

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:50:08 +01:00
Jan Kissig 246f299df1
Bug 35380: Make record sources available in overlay rules
This patch enhances the filters in record overlay rules when using the "Source" module. The available filter values are now system defined in Administration -> Record sources and can be extended by own record souces. The former hard coded values (f.e. batchmod, intranet) are added to the database and cannot be deleted.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:50:08 +01:00
Jan Kissig d7dd72b1a4
Bug 35380: Schema Update
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:49:52 +01:00
Jan Kissig 86443b8437
Bug 35380: Change record sources to by systemdefined
This changes the structure of record source with an is_system column which controls whether this value can be deleted.
Also predefined values are inserted to reflect the default hard coded values from 'Record overlay rules' sources

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:46:15 +01:00
Martin Renvoize 67adbce656
Bug 42010: (RM follow-up) Tidy that was missed during push 2026-03-26 09:35:48 +01:00
Martin Renvoize b0df04264e
Bug 39781: (RM follow-up) Fix wrong mock_userenv key in test
mock_userenv expects 'branchcode' but the test was passing 'branch',
causing the branchcode to fall through to the 'Branch4T' default and
the branch-specific notice lookup to never match.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:32:23 +01:00
Martin Renvoize eb36400a9b
Bug 40136: (follow-up) Stringify DateTime objects in _unblessed_for_log
Koha::Object::AUTOLOAD calls set_column directly, bypassing DBIC's
inflate/deflate mechanism. This means date/datetime column values set
via Koha object accessors (without store()) can end up as raw DateTime
objects in _column_data. When _unblessed_for_log then calls get_column,
it retrieves the DateTime object, which encode_json in C4::Log cannot
serialise, causing the patron delete (and test) to die with an
"encountered object" error.

Fix by stringifying any DateTime objects found in the column data before
returning from _unblessed_for_log.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-26 09:32:16 +01:00
Tomás Cohen Arazi 9e8eceb1a7
Bug 23415: (follow-up) Fallback to 'days' in 'get_chargeable_units'
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
2026-03-25 13:48:06 +01:00
Tomás Cohen Arazi c4c21afa6c
Bug 40136: (follow-up) Fix IsItemIssued.t mock_userenv
mock_userenv was called before the test patron was created and
used the default borrowernumber (51), which doesn't necessarily
exist in the test database. This caused safe_to_delete to crash
on the not_same_branch check when Koha::Patrons->find returned
undef.

Move mock_userenv after patron creation and pass the actual
borrowernumber.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
2026-03-25 12:30:38 +01:00
Tomás Cohen Arazi 5dc1d9ba5e
Bug 40136: (follow-up) Fix wrong `_result_source` call
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-25 11:19:10 +01:00
Martin Renvoize 062d24f101
Bug 40136: (follow-up) Avoid DateTime inflation in _unblessed_for_log
Switch from unblessed() to get_column() so that date/datetime column
values are returned as raw ISO strings directly from DBIC's internal
storage, without constructing DateTime objects that would only be
immediately stringified. This makes _unblessed_for_log cheaper on every
call, which is particularly noticeable in the test suite where
BorrowersLog defaults to enabled and patron stores are frequent.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-25 09:50:27 +01:00
Jonathan Druart 89aa40cc1e
Bug 41460: Fix ALTER TABLE statement
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-24 17:07:35 +01:00
Martin Renvoize 852a8b802f
Bug 40853: (RM follow-up) Fix re-base error 2026-03-24 16:10:01 +01:00
Martin Renvoize caae75e805
Bug 40853: DBRev 25.12.00.036
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:39:09 +01:00
Nick Clemens 02fa01e146
Bug 40853: Add ElasticsearchBoostFieldMatchAmount system preference
This adds a new system preference to control the amount of boost. The default of 0 will act as before this patch.
You can add any positive number, or a number between 0 and 1 for negative boost

To test:
1 - Add a record with
    245 $a novel
2 - Add a record with
    245 $a A novel : $b about things / $c by me
3 - Search for: novel
4 - Search for: a novel
5 - Apply patch, restart all
6 - Repeat searches, note exact titles are boosted
7 - Test with more titles as you are able

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:38:09 +01:00
Nick Clemens 06fa749960
Bug 40853: Unit tests
Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:38:09 +01:00
Martin Renvoize b18fbab806
Bug 39781: (QA follow-up) Fix crashes, dead code and add tests
- Fix potential crash when C4::Context->userenv returns undef:
  use the established ternary guard pattern used throughout other
  TT plugins (AuthorisedValues, Branches, Desks, etc.)
- Remove dead second argument passed to GetLettersAvailableForALibrary,
  which only accepts one argument; the group_by/columns/order_by
  hashref was silently dropped
- Fix typo "librries" -> "libraries" in GetByModuleForLibrary POD
- Update POD example to use 'add_message' module matching actual usage
- Add test coverage for GetByModuleForLibrary, verifying that a user
  logged in to a branch sees branch-specific and all-libraries notices,
  while users at other branches see only all-libraries notices

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:38:09 +01:00
Andrew Fuerste Henry 5937d40a28
Bug 39781: Add POD coverage
Signed-off-by: Juliet Heltibridle <jheltibridle@rcplib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:38:09 +01:00
Andrew Fuerste Henry 3a1bd59197
Bug 39781: Allow limiting of custom message notices by library
To test
The following should work with either email or SMS notices sent from the Add Message button in a patron record
1 - create a notice in module Patrons (custom message) for all libraries
2 - create a notice in module Patrons (custom message) for Centerville
3 - while logged in at Centerville, confirm you can see and enqueue your all libraries message
4 - while logged in at Centerville, confirm you can see but cannot successfully enqueue your Centerville message
5 - while logged in at Fairfield, confirm you can see but cannot successfully enqueue your Centerville message

6 - apply patch, restart_all

7 - while logged in at Fairfield, confirm you can see and enqueue your all libraries message
8 - while logged in at Fairfield, confirm you cannot see your Centerville message
9 - while logged in at Centervielle, confirm you can see and enqueue your Centerville message

Signed-off-by: Juliet Heltibridle <jheltibridle@rcplib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:38:08 +01:00
Martin Renvoize ea5936b15a
Bug 42083: DBRev 25.12.00.035
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:38:08 +01:00
Martin Renvoize 8eec766b5a
Bug 42083: (QA follow-up) Simplify atomicupdate migration loop
Replace the O(n²) accumulate-and-re-insert pattern with a simple
per-row execute inside the while loop.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:35:35 +01:00
Andrew Fuerste Henry 3dd4691076
Bug 42083: Implement distinct permissions
To test:
1 - have an staff account with 'send_messages_to_borrowers' permission but not top level 'borrowers' permission
2 - have a value in SMSSendDriver system preference
3 - log in under your staff account and confirm you can send both email and SMS messages from the Add Message button on a patron record

4 - apply patch, updatedatabase, restart_all

5 - confirm your staff account now has 'send_messages_to_borrowers_email' permission rather than 'send_messages_to_borrowers'
6 - confirm your staff account can now only send email messages from the Add Message button on a patron record
7 - add 'send_messages_to_borrowers_sms' permission to your staff account
8 - confirm your staff account can now send both email and sms messages from the Add Message button on a patron record
9 - remove 'send_messages_to_borrowers_email' from your staff account
10 - confirm your staff account can now only send sms messages from the Add Message button on a patron record

Signed-off-by: Juliet Heltibridle <jheltibridle@rcplib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:35:35 +01:00
Andrew Fuerste Henry 6b51873f9c
Bug 42083: atomicupdate for permissions
Signed-off-by: Juliet Heltibridle <jheltibridle@rcplib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:35:35 +01:00
Andrew Fuerste Henry 153a18d603
Bug 42083: Create distinct permissions
Signed-off-by: Juliet Heltibridle <jheltibridle@rcplib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:35:34 +01:00
Martin Renvoize 9d39d80801
Bug 42010: Escape EDIFACT special characters in BGM purchase order number
Bug 20253 introduced the ability to use the basket name as a purchase order
number in the BGM segment of an EDIFACT order message. However, the basket
name was inserted into the BGM segment without escaping EDIFACT special
characters (apostrophe, colon, plus, question mark).

In EDIFACT, the apostrophe (') is the segment terminator, so any literal
apostrophe in data must be escaped with the release character (?) as ?'.
Without this, a basket name like "Children's Requests" would produce an
invalid EDIFACT message.

This fix applies encode_text() to the purchase order number in
beginning_of_message() so all special characters are properly escaped.

Test plan:
- Run t/db_dependent/Koha/Edifact/Order.t
- All tests should pass, including the new tests covering special character
  escaping in BGM segments

Signed-off-by: Graham Jones <GrahamJ@askewsandholts.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:35:34 +01:00
Nick Clemens 0d84b49ea7
Bug 41959: (QA follow-up) Add missing POD
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:35:34 +01:00
Andreas Jonsson 6fbcedd5db
Bug 41959: Handle edge case
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:35:34 +01:00
Nick Clemens 890ce7cc49
Bug 41959: Unit tests
This adds a test for the condition where there are 2 holds, 1 item, and the item cannot fulfill the first hold

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:35:34 +01:00
Martin Renvoize 97d980a7ba
Bug 41460: DBRev 25.12.00.034
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:35:34 +01:00
Michael Hafen 7cebccb76f
Bug 41460: Remove default value from mediumtext column systempreferences.value
Mysql doesn't allow text type columns to have a default.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 15:26:10 +01:00
Martin Renvoize 1544020e57
Revert "Bug 41247: Add cypress tests"
This reverts commit e838b70be7.
2026-03-24 12:19:31 +01:00
Martin Renvoize 8d14dbe0f1
Revert "Bug 41247: (follow-up) Fix Cypress test after administration rename"
This reverts commit b7eea37e1a.
2026-03-24 12:19:06 +01:00
Martin Renvoize aa895e95c5
Revert "Bug 41247: Preparation: Cypress XML plugin"
This reverts commit 0387ebfb97.
2026-03-24 12:17:58 +01:00
Martin Renvoize 7077634044
Revert "Bug 41247: Improve ILL batches modal resetability"
This reverts commit b591b668d2.
2026-03-24 12:17:41 +01:00
Jonathan Druart a12aac8957
Bug 41521: Silent perlcritic warnings
Stricture disabled at line 67, column 5.  See page 429 of PBP.  (Severity: 5)

We should deal with that later instead of ignoring the warnings

Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:54 +01:00
Jonathan Druart d0ee8f5a6e
Bug 41521: Fix forbidden patterns (trailing spaces and http)
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:53 +01:00
Jonathan Druart 8c46e76045
Bug 41521: Fix spelling
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:53 +01:00
Jonathan Druart bbf43f465e
Bug 41521: Exclude lib/WebService from pod xt tests
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:53 +01:00
Jonathan Druart f9788889f3
Bug 41521: Remove deps from cpanfile
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:53 +01:00
Jonathan Druart b7b85b3eeb
Bug 41521: Reject lib/ from tidy check
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:53 +01:00
Jonathan Druart b4b62d0820
Bug 41521: Remove perl valid warnings
% perl -wc Koha/ExternalContent/OverDrive.pm
Possible precedence issue with control flow operator (die) at /kohadevbox/koha/lib/WebService/ILS/OverDrive/Patron.pm line 365.
Possible precedence issue with control flow operator (die) at /kohadevbox/koha/lib/WebService/ILS/OverDrive/Patron.pm line 573.
Possible precedence issue with control flow operator (die) at /kohadevbox/koha/lib/WebService/ILS/OverDrive/Patron.pm line 643.

Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:53 +01:00
Jonathan Druart 5d516102c8
Bug 41521: Add WebService::ILS to lib/
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:53 +01:00
Hammat Wele 63a1924b27
Bug 33857: Avoid increasing file size while uploading image
This patch prevents image size from increasing while uploading by
keeping the original image format

Test plan:

1. Download the attached image large-image.jpg
  --> It should be ~170kB and 1000×1500 pixels
2. Enable LocalCoverImages in System preferences
3. Find a record in the staff interface
4. Go to "Images" tab
5. Upload large-image.jpg image
   5.1. Click on "Upload"
   5.2. Click on "Drop files here or click to select a file"
   5.3. Choose picture (large-image.jpg)
   5.4. Click on "Process images"
6. Click on image for large image
7. Right-click on the image and save it
  --> It is ~930kB and 600x900 pixels

8. Apply the patch and flush browser cache (Crtl+F5) to update js files
9. Detele the previous uploaded image
10. Repeat step 3, 4, 5, 6, 7
   --> The image is ~92kB and 600x900 pixels
11. prove t/db_dependent/Koha/CoverImages.t
   --> All tests should be successful

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:52 +01:00
Emily Lamancusa 699ba06eb7
Bug 41386: (QA follow-up) Make sure charge is defined
Check that $charge is defined before comparing, to avoid "argument isn't
numeric" error if no charge is defined.

Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:52 +01:00
Emmi Takkinen b33a7c1df4
Bug 41386: Do not allow 0.00 values to be passed from Koha::Hold->cancel
In method Koha::Hold->cancel in the section where we charge a cancel
fee there is a if statement "if $charge" after calling sub add_debit.
This allows value "0.00" to be passed to the add_debit and it then
gets caught in statement "unless ( $amount > 0 )" which calls exception
AmountNotPositive. This happens at least when syspref
ExpireReservesMaxPickUpDelayCharge or circulation rule "Expired hold charge"
has value 0.00 and cronscript cancel_expired_holds.pl is run. This patch
fixes the if statement in Koha::Hold->cancel.

To test:
1. Enable syspref ExpireReservesMaxPickUpDelay.
2. Set syspref ReservesMaxPickUpDelay value as 1.
3. Set syspref ExpireReservesMaxPickUpDelayCharge value as 0.00.
4. Make sure you have a waiting hold which has expiration date further
in the past than ReservesMaxPickUpDelay value in your database. If not,
create one.
5. Run cronscript cancel_expired_holds.pl.
=> Exception 'Koha::Exceptions::Account::AmountNotPositive' thrown
'Debit amount passed is not positive' is raised.
6. Set syspref ExpireReservesMaxPickUpDelayCharge value as 0 and run
cronscript again.
=> No exception is raised.
7. Apply this patch and restart services if needed.
8. Create a new waiting hold with expiration date in the past.
9. Run cronscript again.
=> No exception is raised.
=> Confirm that no cancel fee was added for patron.

Also prove t/db_dependent/Koha/Holds.t.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Bernard Scaife <bernard.scaife@openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:52 +01:00
Martin Renvoize f26e9344b0
Bug 41479: DBRev 25.12.00.033
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 09:55:52 +01:00
Owen Leonard 2e853287f8
Bug 41479: (follow-up) Remove bakertaylorimages.js
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 07:50:32 +01:00
Owen Leonard 41ea86bc27
Bug 41479: Remove Baker & Taylor integration
This patch removes Baker & Taylor integration following the shutdown of
the company.

To test, apply the patch, run the database update, and restart all.

- In the staff interface go to Administration -> System preferences and
  search for 'Baker.' There should be no results.
- If necessary enable at least one cover image service, e.g.
  AmazonCoverImages, OpacCoce, GoogleJackets, etc.
- Log in to the OPAC and confirm that cover images display correctly on
  these pages:
   - Search results
   - Bibliographic detail page
   - Shelf browser
   - Your summary -> Checked out
     - Checkout history
   - Recent comments
   - Lists

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 07:50:10 +01:00
Pedro Amorim 101f292a5a
Bug 41614: available options template params also needed for op=list
Test plan, before applying patch:
1) Visit additional contents for HTML customizations:
<staff_url>/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations
2) Click the left side 'Display location' filter. No options show under OPAC or Staff interface
3) Apply patch, restart plack, refresh. All options should now show (and working filter as expected) under OPAC and Staff interface

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-24 07:48:24 +01:00
Martin Renvoize 46f618d3c4
Bug 40286: (RM follow-up) Make codespell happy 2026-03-23 13:01:25 +01:00
Martin Renvoize 50df5d5aca
Bug 40136: (RM follow-up) Add missing pod coverage for private subs
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 11:26:22 +01:00
Martin Renvoize 863b68b135
Bug 40136: (RM follow-up) Fix logaction to JSON-encode plain hashref infos
Commit 55689d41ba changed Koha::Patron::extended_attributes() and
add_extended_attribute() to pass plain hashrefs to logaction() and
updated tests to expect JSON in the info column. However, logaction()
still had a code path that converted any non-CATALOGUING hashref $infos
to a Dumper() string before the encode_json() at line 151 could fire,
so JSON was never stored and the tests failed.

Remove the catch-all Dumper branch; non-CATALOGUING hashref inputs now
fall through to the existing encode_json() call. The CATALOGUING/MODIFY
biblio-Dumper special case is preserved.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 11:05:59 +01:00
Martin Renvoize 25b7068b49
Bug 40136: (follow-up) Log newly-filled fields when editing a patron
Previously, _unblessed_for_log strips undef/empty fields, so fields
that were blank before an edit were absent from $from_storage. Because
@keys was built only from keys %{$from_storage}, any field that
transitioned from empty to a value was never iterated and the change
went unlogged.

Fix by unioning keys from both old and new state so that added,
changed, and cleared fields are all detected.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:46:30 +01:00
Martin Renvoize f4db130ad4
Bug 40136: (follow-up) Unify patron log entries when attributes change with patron data
Three issues were reported with patron action logging:

1. CREATE followed by extended_attributes() produced two log entries
   (CREATE for patron fields + MODIFY for attributes) instead of one.
2. MODIFY that changed both regular fields and patron attributes produced
   two separate MODIFY entries instead of one unified entry.
3. DELETE log did not include the patron's extended attribute values.

Fix:
- logaction() now explicitly returns the stored Koha::ActionLog object so
  callers can capture the action_id.
- Koha::Patron::store() saves the action_id of the CREATE or MODIFY log it
  writes into $self->{_patron_log_id}.
- Koha::Patron::extended_attributes() setter checks for _patron_log_id. If
  set, it calls _merge_attribute_log() to fold the attribute changes into
  the existing log entry (updating its info and diff JSON) instead of
  creating a new MODIFY entry. This covers both the CREATE and MODIFY cases.
- Koha::Patron::delete() now iterates extended_attributes before deletion
  and adds attribute.CODE keys to the patron_data snapshot, so the DELETE
  diff includes the removed attributes.
- New private helper _merge_attribute_log() decodes an existing action log
  entry's info/diff JSON, merges in the attribute change data using
  Struct::Diff, and saves the updated entry.
- Tests added to t/db_dependent/Koha/Patrons.t covering all three scenarios.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:46:22 +01:00
Lisette Scheer 7287b091a4
Bug 40136: (QA follow-up) Fix tests
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:42:34 +01:00
Martin Renvoize 543ebdc2ff
Bug 40136: (follow-up) Strip undef and empty fields from patron log data
Exclude fields with undef or empty string values from _unblessed_for_log
so that CREATE and DELETE log diffs only show fields with meaningful values.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:42:30 +01:00
Martin Renvoize 55689d41ba
Bug 40136: (follow-up) Fix extended_attributes logaction to populate diff column
The extended_attributes setter and add_extended_attribute both logged
attribute changes using the old custom {before, after} format passed as
the info string, with no $original argument, so the diff column was never
populated.

Split the existing $change->{before}/$change->{after} into separate
$log_from/$log_to hashrefs and call logaction with the standard signature,
so Struct::Diff generates the diff column as with other MEMBERS/MODIFY
entries.

Update tests to reflect the new info column format (after-state only)
and remove a stray debug Data::Dumper/print left in the test.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:42:26 +01:00
Martin Renvoize 9ae4911704
Bug 40136: (follow-up) Refactor MODIFY log to use _unblessed_for_log
Replace the manual unblessed + skip_fields + blessed-stringify logic in
the MODIFY actionlog section with calls to _unblessed_for_log, which
already handles all three concerns. This removes the need for @skip_fields,
the dateexpiry normalisation workaround, and the inline blessed checks.

Also restrict the log_from/log_to hashrefs to only the changed keys, so
the diff column contains only fields that actually changed rather than
the full patron state.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:42:22 +01:00
Martin Renvoize f83424d3dd
Bug 40136: Populate diff column for patron CREATE, MODIFY and DELETE logs
Previously MEMBERS/CREATE and MEMBERS/DELETE logged empty info with no
diff, and MEMBERS/MODIFY stored a custom {field:{before,after}} JSON in
the info column with no diff column entry.

This updates all three to use the standard logaction pattern:
- CREATE: diff({}, patron_data) — all fields shown as added
- MODIFY: diff(old_state, new_state) via Struct::Diff; info column now
  holds the full updated patron state (consistent with other modules)
- DELETE: diff(patron_data, {}) — all fields shown as removed
- MODIFY_CARDNUMBER: likewise uses standard pattern with diff column

Password, lastseen and updated_on are excluded from all log payloads.
DateTime objects (inflated date/datetime columns) are stringified to
their ISO representation before JSON serialisation.

Tests updated to verify changes via the diff column rather than the
former custom info format.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:42:19 +01:00
Lisette Scheer 9d20ea636a
Bug 20956: (QA follow-up) Restore info pretty
I restored the pretty code for the info file because the page needs to be able to wrap, like it does for other logs.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:42:16 +01:00
Martin Renvoize f528142daa
Bug 20956: (follow-up) Nest permissions data under 'permissions' key in MEMBERS/MODIFY log
Changes to patron permissions via member-flags.pl are logged as MEMBERS/MODIFY,
indistinguishable from regular patron field edits. This patch wraps the
before/after permission hashes under a 'permissions' key so that the diff
renderer displays changes as 'permissions.cataloguing' etc., making it clear
the diff relates to permissions rather than first-level patron fields.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:42:12 +01:00
Ayoub Glizi-Vicioso 182ccd1c0b
Bug 20956: Fix test failures
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:42:08 +01:00
Martin Renvoize cd34882803
Bug 20956: (QA follow-up) Match getuserflags() return structure
This patch changes Koha::Patron->permissions() to return the same
structure as C4::Auth::getuserflags() for architectural consistency.

Previously, permissions() only returned granted permissions:
{
  catalogue => 1,
  circulate => { override_renewals => 1 }
}

Now it returns ALL flags (matching getuserflags()):
{
  superlibrarian => 0,
  catalogue => 1,
  circulate => { override_renewals => 1 },
  borrowers => 0,
  ...
}

This provides several benefits:

1. Consistency - Matches established C4::Auth::getuserflags() pattern
2. Simpler checks - Can use "if ($flags->{module})" instead of
   "if (exists $flags->{module})"
3. Future compatibility - Enables eventual deprecation of getuserflags()
   in favor of the ORM-based $patron->permissions()
4. Helper compatibility - Works with existing _dispatch() logic

The logging functionality still works correctly - the diff will now
show explicit changes from 0 to 1 rather than absence to presence.

Test plan:
1. prove t/db_dependent/Koha/Patron.t
2. prove t/db_dependent/Log.t
3. Verify all tests pass
4. Confirm permissions() now returns all flags with 0/1 values

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:42:05 +01:00
Martin Renvoize ae60527f91
Bug 20956: (QA follow-up) Fix tests and refactor to use ORM
This patch addresses several QA concerns:

1. Fixed duplicate subtest name - renamed second "is_superlibrarian() tests"
   to "permissions() tests" which accurately reflects what it tests

2. Fixed incorrect hash assignment - permissions() returns a hashref,
   not a hash. Changed `my %permissions` to `my $permissions`

3. Improved test coverage - expanded from 2 to 8 assertions covering:
   - Hashref return type verification
   - Single flag permission
   - No permissions
   - Multiple flags
   - Granular subpermissions from user_permissions table

4. Refactored permissions() method to use DBIx::Class ORM instead of
   raw SQL queries, following Koha::Object architecture principles

All tests now pass without warnings.

Test plan:
1. prove t/db_dependent/Koha/Patron.t
2. prove t/db_dependent/Log.t
3. Verify all tests pass
4. Confirm the test now properly validates the intended behavior

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:42:02 +01:00
Hammat Wele c092059a77
Bug 20956: (follow-up) Tidyness and fix no pod coverage in Koha/Patron.pm
Signed-off-by: Hammat Wele <hammat.wele@inlibro.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:41:58 +01:00
Kyle M Hall 489f19a410
Bug 20956: (QA follow-up) Move subs to Koha::Patron, add unit tests
Subroutines in members/member-flags.pl are combined and moved to Koha::Patron

Removed the html-based english description of each permission from the logging

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:41:55 +01:00
Hammat Wele 553a18da09
Bug 20956: BorrowersLog is not logging permission changes
Changes to permissions are not logged in the Koha action_logs, making it impossible to trace changes to staff permissions. This patch ensures that all changes to permissions are logged.

Test plan:

    1. Go to a staff interface
    2. Search for a patron and change their permissions (note their borrowernumber)
    3. Go to tools/Log viewer
    4. Filter by Module = Patrons, Object = <borrowernumber>.
    5. Click on submit
       ==> No permission changes are logged.
    6. Apply the patch
    7. Repeat step 1, 2, 3, 4, 5
       ==> Permission changes are now logged
    8. Check that the diff column contains the change:
       select diff from action_logs where object= <borrowernumber> order by action_id desc limit 1;

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:41:51 +01:00
Martin Renvoize 9837925379
Bug 40135: (follow-up) Add tests for Koha::Biblio::_unblessed_for_log
Add a '_unblessed_for_log() tests' subtest to t/db_dependent/Koha/Biblio.t
that verifies:
- the method returns a plain hashref
- fields with real values (biblionumber, title, author) are present
- timestamp is excluded
- undef fields are excluded
- empty-string fields are excluded
- the ADD action log diff column is populated and does not contain timestamp

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:41:48 +01:00
Martin Renvoize a8578c93f0
Bug 40135: (follow-up) Strip undef and empty fields from biblio log data
Add _unblessed_for_log to Koha::Biblio, mirroring the same method in
Koha::Patron (bug 40136). It excludes undef/empty-string columns and the
auto-updated timestamp field so that biblio diffs only contain meaningful
values.

Update AddBiblio, ModBiblio and DelBiblio in C4/Biblio.pm to use
_unblessed_for_log instead of raw unblessed when building the hashrefs
passed to logaction, eliminating noise from null columns in ADD/MODIFY/DELETE
diffs.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:41:45 +01:00
Martin Renvoize ef7dde45f7
Bug 40135: Fix info column prefix for biblio MODIFY logs
The MODIFY path in logaction received the updated biblio as a plain
hashref (not a Koha::Object), so the info column was populated with
raw Dumper output instead of the expected "biblio ..." prefix.

This broke two things: the 'Modification Log' link from the bib record
sidebar (which filters on info LIKE 'biblio%'), and the Object column
in the log viewer (which detects biblio entries via info.substr(0,6)).

Following the same pattern as the Koha::Item MODIFY special case, add
a parallel branch for CATALOGUING MODIFY with a hashref \$infos that
sets the info column to "biblio " . Dumper(\$original). This restores
backward-compatible detection while keeping the diff column correctly
populated from the before/after hashrefs (including _marc changes).

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:41:41 +01:00
Martin Renvoize f8f653f71e
Bug 40135: Include MARC field changes in CATALOGUING diff logs
The previous commit populated the diff column for biblio ADD/MODIFY/DELETE
logs with changes to biblio table columns (title, author, frameworkcode,
etc.), but lost the MARC-level change tracking that previously stored a
pre-change MARC dump in the info column.

This follow-up extends the diff to also capture MARC field changes by
including a synthetic _marc key in the before/after hashrefs passed to
logaction. MARC fields are represented as tag => array-of-formatted-strings
(e.g. "10 $a Title" for data fields, scalar for control fields), allowing
Struct::Diff to produce readable field-level diffs.

The _marc entries appear alongside biblio column changes in the existing
diff table in the log viewer, using dotted keys like _marc.245.0.

A helper _marc_record_to_diffable() converts a MARC::Record to this
diffable hashref. All three functions (AddBiblio, ModBiblio, DelBiblio)
use record_strip_nonxml (wrapped in eval) to safely capture the MARC even
for records with invalid XML characters, without producing warnings or
affecting the success of the main operation.

C4/Log.pm is also updated to Dumper() a plain hashref passed as $infos,
so the info column contains useful data rather than a stringified ref.

Tests are extended to verify that the diff column is populated and that
_marc changes are captured when MARC content actually changes.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:41:38 +01:00
Martin Renvoize f16d5f3ad4
Bug 40135: Populate diff column for biblio ADD/MODIFY/DELETE logs
Previously the CATALOGUING action log entries for biblio operations
stored minimal data: ADD and DELETE logged just the string "biblio",
while MODIFY logged a raw MARC-formatted dump of the pre-change record
as a plain text string. None of these populated the diff column.

This follow-up updates AddBiblio, ModBiblio and DelBiblio in C4/Biblio.pm
to follow the same pattern as Koha::Item::store, passing the Koha::Biblio
object and an original unblessed hashref to logaction so that the diff
column is automatically populated with Struct::Diff JSON reflecting the
changes to the biblio table columns (title, author, frameworkcode, etc.).

For ADD, logaction diffs an empty hashref against the new biblio state,
producing added-field entries for all columns. For DELETE, it diffs the
pre-deletion state against an empty hashref, producing removed-field
entries. For MODIFY, the before-state is captured prior to the update and
the after-state is read back once ModBiblioMarc and _koha_modify_biblio
have completed, so the diff reflects the actual committed changes.

The test for ModBiblio on an invalid MARC record is updated to reflect
that the MARC decoding error is no longer logged in the info column;
the biblio table data is now logged instead.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:41:35 +01:00
Martin Renvoize 788d1b455f
Bug 36698: Expand nested object values in diff table
When the diff column contains a value that is itself an object (e.g.
a set of granular subpermissions such as
{"override_renewals":1,"manage_curbside_pickups":1}), the previous
renderer would JSON.stringify it into a single unreadable cell.

This follow-up makes fmt() recurse into objects, rendering them as
an indented <ul> list of key: value pairs instead. The <del>/<ins>
colour wrapper then highlights the entire expanded block, making it
clear at a glance which subpermissions were added or removed.

null values (e.g. suspend_until transitioning to/from null) are
rendered as empty, letting the red/green cell background convey
the presence or absence of the value.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:41:31 +01:00
Martin Renvoize 56a8a9a934
Bug 36698: Render diff column as a human-readable table in the log viewer
Previously the diff column in the log viewer displayed raw Struct::Diff
JSON (e.g. {"D":{"superlibrarian":{"N":1,"O":0}}}), which was not
useful to end users.

This follow-up adds a JavaScript renderer (renderStructDiff) to
viewlog.js that parses the Struct::Diff JSON and presents it as a
compact Field / Before / After table, reusing the existing <del>/<ins>
colour styling already present for the jsdiff compare feature. Nested
diffs (e.g. granular subpermissions) are flattened using dot-separated
key names. Non-Struct::Diff content falls back gracefully to a <pre>
block so that entries from other modules are not affected.

The change benefits all modules that currently populate the diff column:
HOLDS (MODIFY/SUSPEND/RESUME/CANCEL/FILL), CATALOGUING (MODIFY),
APIKEYS (MODIFY/REVOKE/ACTIVATE), and MEMBERS (MODIFY for permissions).

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 10:41:23 +01:00
Kyle M Hall bf0c748315
Bug 41107: (QA follow-up) Tidy status.t
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 09:33:54 +01:00
Tomás Cohen Arazi f64051cddb
Bug 41107: (follow-up) Use Koha::Status in Koha::Template::Plugin::Koha
This patch makes the template plugin used for rendering the Koha version
use the introduced method.

I did this checks:

```shell
cd koha-tmpl
git grep Koha.Version
```

The places Koha.Version is used, it is used as:

* `Koha.Version.maintenance`
* `Koha.Version.release`
* Assigned to a variable like this `SET koha_version = Koha.Version`

All occurences of the `koha_version` template variable are:

* `koha_version.development`
* `koha_version.major`
* `koha_version.minor`

This implies the newly added `.version` attribute has no undesired side
effects in templates.

Note: plugins-home.tt has a different use of a variable named the same,
which is unrelated.

To test:
1. Apply this patch
2. Run:
   $ ktd --shell
  k$ prove t/Koha_Template_Plugin_Koha.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 09:33:54 +01:00
Tomás Cohen Arazi 68c8b8f13f
Bug 41107: Add /status/version API endpoint
Adds a new REST API endpoint to retrieve Koha version information.

This implementation includes:
- Koha::Status class for instance status information
- /status/version endpoint returning structured version data
- OpenAPI specification with koha_version definition
- REST controller for handling the endpoint

The returned version object includes:
- version: full version string (e.g., '25.06.00.029')
- major: major version number
- minor: minor version number
- release: major.minor version
- maintenance: major.minor.maintenance version
- development: development version (if applicable)

This prepares the foundation for a future /status endpoint that will
include all information displayed in the about page.

To test:
1. Apply the patch
2. Run:
   $ ktd --shell
  k$ yarn api:bundle
3. Run the tests:
  k$ prove t/Koha/Status.t \
           t/db_dependent/api/v1/status.t
=> SUCCESS: Tests pass!
4. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 09:33:54 +01:00
Martin Renvoize 7929a1198e
Bug 41814: DBRev 25.12.00.032
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 09:33:54 +01:00
Martin Renvoize 41e729baba
Bug 41814: (RM follow-up) Add PatronAgeRestriction to UsageStats
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 09:17:03 +01:00
Katrin Fischer 8948d7117a
Bug 41814: (QA follow-up) Add bug number to atomic update and some style fixes
Sponsored-by: Cheshire West and Chester Council
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 09:16:59 +01:00
Jake Deery c1f0d97e8b
Bug 41814: Add PatronAgeRestriction syspref and code
This patch adds a new PatronAgeRestriction, which acts in the same way
PatronSelfRegistrationAgeRestriction (a restriction on the maximum age
of a borrower), except for the intranet. It will also act as the
maximum age restrictor on the OPAC, in the event that the
PatronSelfRegistrationAgeRestriction is not set.

To test:
== APPLY PATCH ==
a)  updatedatabase
b)  Set the PatronAgeRestriction syspref to 20, set the
    PatronSelfRegistrationAgeRestriction to 30
c)  On the OPAC, try to create or modify a patron whose age is 35 years
    *)  Note the error message presented, suggesting the age exceeds the
    maximum
d)  On the OPAC, try to create or modify a patron whose age is 25 years
    *)  Note the submission succeeds
e)  On the staff client, try to create or modify a patron whose age is
    25 years *)  Note the error message presented, suggesting the age
    exceeds the maximum
f)  Set the PatronSelfRegistrationAgeRestriction to empty
g)  On the OPAC, try to create or modify a patron whose age is 25 years
    *)  Note the error message presented, suggesting the age exceeds
    the maximum
h)  On the OPAC, try to create or modify a patron whose age is 19 years
    *)  Note the submission succeeds
i)  Set the PatronAgeRestriction to empty
j)  On the staff client, try to create or modify a patron whose age is
    75 years
    *)  Note the submission succeeds
k)  On the OPAC, try to create or modify a patron whose age is 75 years
    *)  Note the submission succeeds
== SIGN OFF ==

Sponsored-by: Cheshire West and Chester Council
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 09:16:52 +01:00
Martin Renvoize 23c88ff85b
Bug 40286: RM follow-up: Add POD coverage for private subs in C4/Auth.pm
Adds POD documentation for _version_check, _timeout_syspref,
_get_session_params, and _dispatch to fix pod_coverage QA failures.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 08:56:58 +01:00
Tomás Cohen Arazi 9f08e3a1c3
Bug 40286: (follow-up) Fix password_validation.t for find_by_identifier behavior
The change to use find_by_identifier in checkpw_internal means userid
is now checked before cardnumber. This updates the test to reflect that
when an identifier matches both a userid and cardnumber, the userid
match takes precedence.

To test:
1. Apply this patch
2. Run:
   $ ktd --shell
   k$ prove t/db_dependent/api/v1/password_validation.t
=> SUCCESS: Tests pass!

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 08:01:09 +01:00
Tomas Cohen Arazi bf0ed60e73
Bug 40286: Make C4::Auth::checkpw_internal use Koha::Patrons->find_by_identifier
This patch does what the title says. The code change is clear to
understand.

The check for userid vs. cardnumber is done in the `find_by_identifier`
method so no need to do it here.

Note: When `find_by_identifier` was written, I picked some other place's
logic (API password validation) so `userid` is used first for finding
the patron. Because of this, tests in t/db_dependent/Auth.t need to be
adjusted. I believe this is not an issue, as those tests should be
removed once we move forward with bug 33905, entirely.

To test:
1. Apply this patch
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Auth.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 08:01:01 +01:00
Martin Renvoize 91f3e51e1a
Bug 40962: Apply ViewPolicy filter consistently in OAI Server test setup
The test built @marcxml and @oaidc expected data using metadata_record()
with no parameters, while the OAI server's get_biblio_marcxml() always
calls metadata_record({ interface => 'opac' }), which applies the
ViewPolicy filter. On nodes where the MARC framework has non-default
hidden values (or a stale cache entry), ViewPolicy strips fields like 245,
causing the expected and actual responses to diverge.

Fix by using interface => 'opac' when building the expected data, and
deriving dc:title from the filtered MARC record rather than hardcoding it,
so both sides of every comparison go through the same code path.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 07:54:05 +01:00
Martin Renvoize cde9628b86
Bug 40962: Fix race condition in OAI Server test for OpacHiddenItems
The 'Tests for OpacHiddenItems' subtest captured Perl's wall clock time
*after* calling build_sample_item(), then used that timestamp as both the
expected OAI datestamp and the 'from' filter. On slow systems the INSERT
can straddle a second boundary, so MySQL records T:40 while dt_from_string
returns T:41, causing noRecordsMatch on the ListRecords tests and a
datestamp mismatch on the GetRecord tests.

Fix by calling $item->discard_changes and deriving $utc_timestamp from the
timestamp MySQL actually assigned, matching the pattern already used in the
Bug 19725 subtest in the same file.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-23 00:22:16 +01:00
Martin Renvoize ad1caed65c
Bug 23415: DBRev 25.12.00.031
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:59:25 +01:00
Martin Renvoize 38f2989627
Bug 23415: (RM follow-up) Add new sysprefs to HEA data sharing
Add FineNoRenewals, FineNoRenewalsBlockAutoRenew,
FineNoRenewalsBlockSelfCheckRenew, and FineNoRenewalsIncludeCredits
to the list of system preferences shared with the Hea (Koha Usage
Stats) service.
2026-03-22 23:52:15 +01:00
Martin Renvoize 7367ac3273
Bug 23415: (RM follow-up) Add POD coverage for private subs in C4/Circulation.pm
Adds POD for _check_max_qty, _calculate_new_debar_dt,
_CalculateAndUpdateFine, and _CanBookBeAutoRenewed to fix QA pod_coverage
failures.
2026-03-22 23:52:15 +01:00
Martin Renvoize a359ed47b1
Bug 23415: (follow-up) Fix typo 'oweing' -> 'owing' in templates and notices
This patch corrects a typo in the auto renewal error code and related
notice templates:
- Adds database update to fix existing notice templates in letter table
- Fixes sample notices for new installations
- Fixes renew_strings.inc template include

The error code changes from 'auto_too_much_oweing' to 'auto_too_much_owing'
throughout.
2026-03-22 23:52:15 +01:00
Martin Renvoize 1eb1cfdbef
Bug 23415: (follow-up) Rename sysprefs and add self-checkout control
This follow-up patch addresses feedback from the QA process:

1. System preference naming consistency
   Renamed the following preferences to remove "OPAC" prefix and align
   with the FineNoRenewals naming convention:
   - OPACFineNoRenewalsIncludeCredits → FineNoRenewalsIncludeCredits
   - OPACFineNoRenewalsBlockAutoRenew → FineNoRenewalsBlockAutoRenew

2. Self-checkout backward compatibility
   Added new system preference FineNoRenewalsBlockSelfCheckRenew to
   control whether fine limits block renewals via web-based self-checkout.

   Previously, OPACFineNoRenewals did not block self-checkout renewals.
   With the rename to FineNoRenewals, this behavior changed. The new
   preference defaults to "0" (don't block) to maintain backward
   compatibility, allowing sites to opt-in to blocking self-checkout
   renewals when patrons exceed the fine threshold.

Test plan:
1. Run prove t/db_dependent/Circulation.t - all tests should pass
2. Run prove t/db_dependent/api/v1/checkouts.t - all tests should pass
3. Verify system preferences are correctly renamed in the admin interface
4. Test self-checkout renewals with FineNoRenewalsBlockSelfCheckRenew
   set to 0 (default) - renewals should work even when fines exceed limit
5. Test self-checkout renewals with FineNoRenewalsBlockSelfCheckRenew
   set to 1 - renewals should be blocked when fines exceed limit

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:52:06 +01:00
Martin Renvoize 06fd7ed7e4
Bug 23415: (follow-up) Respect OPACFineNoRenewalsIncludeCredits preference
This patch fixes a regression introduced in the previous commits where
we changed FineNoRenewals checking to use non_issues_charges instead
of balance. While this change was correct for focusing on blocking
charges, we inadvertently removed the handling of the
OPACFineNoRenewalsIncludeCredits preference.

The OPACFineNoRenewalsIncludeCredits preference controls whether
unapplied account credits should be included when calculating if a
patron exceeds the FineNoRenewals threshold. When this preference is
enabled, the patron's net balance (debits minus credits) should be
used instead of just their outstanding debits.

This patch restores the preference handling for both:
1. Manual renewals in CanBookBeRenewed()
2. Auto renewals in _CanBookBeAutoRenewed()

When OPACFineNoRenewalsIncludeCredits = 1:
  Use balance (all charges net of credits)
When OPACFineNoRenewalsIncludeCredits = 0:
  Use non_issues_charges (only blocking charges, no credits)

This ensures that patrons with sufficient credits can renew their
items when the preference is enabled, while still maintaining the
focus on non-issuing charges introduced in the previous commits.

Test plan:
1. Run prove t/db_dependent/Circulation.t
2. All tests should pass, particularly the
   "auto_too_much_owing | OPACFineNoRenewalsBlockAutoRenew &
   OPACFineNoRenewalsIncludeCredits" subtest

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:27:26 +01:00
Martin Renvoize 8b35dfcc9d
Bug 23415: Add debt override support to circulation checkout page
This patch implements fine override functionality for the checkouts
table on the circulation page (circ/circulation.pl), completing the
debt override feature for all renewal workflows.

Changes:
- Added override_debt checkbox to checkouts-table.inc when
  AllowFineOverrideRenewing preference is enabled
- Updated /svc/renew to accept and process override_debt parameter,
  matching the pattern used for override_limit
- Enhanced circulation-api-client.js to pass override_debt parameter
  through the renewal API call
- Implemented specific CSS classes for renewal error types
  (renewals-allowed-too_many, renewals-allowed-too_much_owing, etc.)
  to enable selective checkbox control
- Modified override checkbox handlers to selectively enable only
  relevant renewal checkboxes based on the specific blocking error:
  * override_limit enables checkboxes for 'too_many' and 'on_reserve'
  * override_debt enables checkboxes for 'too_much_owing' and
    'auto_too_much_owing'
- Added AllowFineOverrideRenewing JavaScript variable to circulation.tt

This provides clear visual feedback to staff about which renewals will
be affected by each override option, improving usability and reducing
confusion when multiple items have different blocking reasons.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:27:26 +01:00
Martin Renvoize 4b88b1de3d
Bug 23415: Fix FineNoRenewals to use non_issues_charges and improve template
This commit addresses two issues:

1. Fixes FineNoRenewals logic to properly use non_issues_charges
   Both CanBookBeRenewed() and _CanBookBeAutoRenewed() were incorrectly
   using the patron's total balance when checking against FineNoRenewals.
   They now use non_issues_charges, which returns only charges that
   should restrict checkouts (e.g., overdues, lost items) as defined by
   debit types with restricts_checkouts=1. This ensures charges like
   rental fees don't inappropriately block renewals.
2. Improves renewal template for AllowFineOverrideRenewing
   - Adds special handling for auto_too_much_owing error to check
     AllowFineOverrideRenewing preference instead of
     AllowRenewalLimitOverride
   - Adds missing id="barcode" attribute to input field for Cypress tests
   - Adds .dialog CSS class to error/success divs for consistent styling
   - Fixes HTML structure with proper <ul><li> wrapping
   - Adds btn btn-default classes to override button
3. Fixes Cypress test issues
   - Corrects data structure access: items[0] instead of item
   - Uses external_id instead of barcode (API field name)
   - Improves form submission selector specificity
   - Uses REPLACE INTO for system preference setup

Test plan:
1. Run t/cypress/integration/Circulation/FineNoRenewals_spec.ts
2. Verify 4 out of 5 tests pass (one remaining template comparison issue)
3. Manually test that patrons with non-issue charges (rental fees) can
   renew when those charges alone don't exceed FineNoRenewals
4. Verify that patrons with issue-blocking charges (overdues) are
   properly blocked from renewal when exceeding FineNoRenewals

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:27:26 +01:00
Martin Renvoize 018e65c811
Bug 23415: Add REST API support for fine override on renewal
This commit adds support for the AllowFineOverrideRenewing functionality
to the REST API renewal endpoints using the standard x-koha-override pattern.

Changes:
1. Added x-koha-override header parameter to both renewal endpoints:
   - POST /checkouts/{checkout_id}/renewal
   - POST /checkouts/{checkout_id}/renewals
2. Updated Koha::REST::V1::Checkouts::renew() to:
   - Check for 'debt_limit' in the overrides stash
   - Check AllowFineOverrideRenewing preference when override is requested
   - Allow renewal override only when both:
     * x-koha-override header contains 'debt_limit' AND
     * AllowFineOverrideRenewing preference is enabled
   - Follows the established x-koha-override pattern used by other endpoints
3. Updated OpenAPI specification (swagger) to document the new header
   using the standard pattern with enum values

Usage:
  curl -X POST /api/v1/checkouts/123/renewal \
    -H "x-koha-override: debt_limit"

The override only works for too_much_owing errors and requires
the AllowFineOverrideRenewing system preference to be enabled.

This implementation follows the same pattern as holds and other
endpoints that support overrides, maintaining consistency across
the REST API.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:27:26 +01:00
Martin Renvoize dcc946473b
Bug 23415: Add API tests for fine override on renewal
This commit adds REST API tests to verify the
x-koha-override: debt_limit functionality for renewals.

Tests cover:
- Renewal blocked due to excessive fines
- Override fails when AllowFineOverrideRenewing is disabled
- Override succeeds when AllowFineOverrideRenewing is enabled
- Both /renewal and /renewals endpoints

Test plan:
1. Run: prove t/db_dependent/api/v1/checkouts.t
2. All 110 tests should pass

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:27:17 +01:00
Martin Renvoize caa3922d27
Bug 23415: Add Cypress test coverage for FineNoRenewals
This commit adds end-to-end tests for the fine-based
renewal blocking functionality using Cypress.

Test coverage includes:
1. Blocking renewal when patron fines exceed FineNoRenewals limit
2. Displaying override button when AllowFineOverrideRenewing is enabled
3. Hiding override button when AllowFineOverrideRenewing is disabled
4. Successful override when permission is enabled
5. Allowing renewal when fines are below the limit

The test creates test data including:
- A patron with checkouts
- Account fines that exceed the FineNoRenewals threshold
- Tests both blocking and override scenarios

Tests verify the UI behavior matches the business logic:
- Error messages are displayed correctly
- Override buttons appear based on preference setting
- Override functionality works as expected
- Security is maintained (no override without permission)

To run these tests:
  npx cypress run --spec "t/cypress/integration/Circulation/FineNoRenewals_spec.ts"

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:15:46 +01:00
Martin Renvoize 58b560d9f9
Bug 23415: (QA follow-up) Fix spelling: oweing -> owing
Standardizes error code spelling across the codebase:
- Changes 'too_much_oweing' to 'too_much_owing'
- Changes 'auto_too_much_oweing' to 'auto_too_much_owing'

The word "owing" is the correct spelling in English.

Files updated:
- C4/Circulation.pm
- C4/SIP/Sip/MsgType.pm
- circ/renew.pl
- koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt
- koha-tmpl/intranet-tmpl/prog/js/checkouts.js
- koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-reserve.tt
- koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt
- misc/cronjobs/automatic_renewals.pl
- opac/opac-reserve.pl
- opac/opac-user.pl
- t/db_dependent/Circulation.t

All tests should pass successfully.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:15:45 +01:00
Martin Renvoize 160b1556b6
Bug 23415: (QA follow-up) Fix logic and security issues
This commit addresses several issues found in QA review:

1. Fixed logic bug in C4::Circulation::CanBookBeRenewed
   - Removed incorrect check of AllowFineOverrideRenewing preference
   - The function now always returns 'too_much_oweing' error when
     patron balance exceeds FineNoRenewals limit
   - AllowFineOverrideRenewing should only control UI override
     capability, not the core renewal check
2. Added permission check in circ/renew.pl
   - Override is now only allowed when both override_debt parameter
     is set AND AllowFineOverrideRenewing preference is enabled
   - Prevents security issue where staff could bypass the preference
     by crafting POST requests
3. Fixed template to conditionally display override button
   - Override button in renew.tt now only shows when
     AllowFineOverrideRenewing is enabled
   - Prevents confusion when override is not permitted
4. Added test coverage
   - Tests verify CanBookBeRenewed behavior with AllowFineOverrideRenewing
     both enabled and disabled
   - Confirms error is always returned regardless of preference setting
5. Fixed minor issues
   - Fixed typo: "he patron" -> "the patron" in checkouts.js
   - Fixed typo: OPACFineNoRenewalsIncludeCredit ->
     OPACFineNoRenewalsIncludeCredits in test

All tests should pass successfully.

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:15:45 +01:00
Emmi Takkinen e06e59bee4
Bug 23415: Fix QA issues
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:15:45 +01:00
Emmi Takkinen f2b7a744b8
Bug 23415: Add new syspref AllowFineOverrideRenewing
This patch adds new syspref AllowFineOverrideRenewing. It allows
staff to renew items for patrons whose fines are over limit set
in syspref FineNoRenewals. Otherwise renewing is prevented. If
renewing items is allowed staff is still required to confirm
if they really want to renew items for the patron.

To test:
1. Apply this patch, update database and restart all services.
2. Find patron with fines over limit set in FineNoRenewals and
renewable items.
=> Note that renewing is prevented with message "Cannot renew,
the patron has a debt of..."
3. Set syspref AllowFineOverrideRenewing as "Allow".
=> Note that items can now be renewed and renewing them displays
confirmation pop-up.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-of-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 23:15:42 +01:00
Martin Renvoize 167a3da258
Bug 23415: (QA follow-up) Move to Circulation::CanBookBeRenewed
This moves the basic calculation for too_much_oweing out of the
controller and into the class method.

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
2026-03-22 23:13:49 +01:00
Emmi Takkinen 00350eacb4
Bug 23415: Rename OPACFineNoRenewals
OPACFineNoRenewals no longer applies just to OPAC.
To avoid confusion rename it as FineNoRenewals and
also rename all occurences in code.

To test, confirm everything works as described in
previous test plans.

Also prove t/db_dependent/Circulation.t

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Shi Yao Wang <shi-yao.wang@inlibro.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
2026-03-22 23:08:28 +01:00
Emmi Takkinen 2589c5ef9d
Bug 23415: Notify patron fines when renewing
It is possible to renew items for patron who has fines over accepted
limit on renew page or from checkout list. This patch adds confirmation
alerts to these renew actions when patron has fines over
"noissuescharge" syspref.

Test plan:
1. Have patron with checkouts and fines over allowed limit
2. Renew checkouts either from renew page or checkout list
=> renew is successful
3. Apply patch
4. Repeat steps 1 and 2
=> On renew page an alert is displayed and user has to confirm
renew
=> When renew is done from checkout list confirmation pop-up
is displayed

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Shi Yao Wang <shi-yao.wang@inlibro.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
2026-03-22 23:03:05 +01:00
Martin Renvoize b91ff527a2
Bug 23269: (RM follow-up) Strip item_group and item_level_holds_count from hold POST body
Bug 23269 added item_group and item_level_holds_count to the hold
response schema (hold.yaml). buildSampleObject reads hold.yaml to
generate test hold objects, so these fields are now included in the
generated object. However, the holds POST endpoint uses an inline
schema with additionalProperties: false that does not include these
fields, causing a 400 validation error when insertSampleHold tries
to create a hold via the API.

Add item_group and item_level_holds_count to the destructuring
strip-list in insertObject so they are excluded from the POST body.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-22 06:44:52 +00:00
Martin Renvoize 9ed90d53fb
Bug 41801: (RM follow-up) Add POD for _cache 2026-03-21 20:28:21 +00:00
Nick Clemens 48386d8570
Bug 41801: (QA follow-up) Ensure toggling priority sets correct rank 2026-03-21 20:28:16 +00:00
Martin Renvoize e0bdf3e859
Bug 41801: (QA follow-up) Prevent race condition with txn_do and FOR UPDATE
FixPriority has a read-modify-write structure: it SELECTs all active
holds for a biblionumber, computes new priority order in Perl, then
issues N UPDATEs.  Two concurrent callers (e.g. two patrons adjusting
holds on a popular title at the same moment) could both read the same
snapshot and then overwrite each other's renumbering, leaving duplicate
or incorrect priority values.

Fix by:

1. Wrapping the entire function body in txn_do so it runs atomically.
   txn_do uses MySQL savepoints when called inside an existing
   transaction, so callers that already hold a transaction are unaffected.

2. Adding FOR UPDATE to the initial SELECT so the first caller
   acquires row-level locks on all holds for that bib.  A second
   concurrent caller will block at the SELECT until the first commits,
   then reads the already-corrected priorities and produces a consistent
   result.

Note: FOR UPDATE locks only rows that exist at read time; a concurrent
INSERT of a brand-new hold can still slip through, but priority
assignment for new holds goes via AddReserve → FixPriority, which will
also acquire the lock and renumber correctly.
2026-03-21 20:28:16 +00:00
Martin Renvoize 4d61ac803f
Bug 41801: (QA follow-up) Replace MAX subquery with in-memory array count for rank clamping
The lowestPriority rank-clamping block fired a second SQL query to get
max(priority) WHERE lowestPriority=0 and used that value as both a
threshold and a splice index.  This was fragile: if priorities had
become discontiguous (e.g. due to a race or a bug elsewhere), the DB
priority value would not match the 1-based position in @priority, and
the splice could land the hold in the wrong slot.

The @priority array already contains all the information needed: it is
sorted lowestPriority ASC so the count of rows where lowestPriority=0
is exactly the number of non-lowest holds, which equals the last valid
rank for a normal hold.  Fetch lowestPriority in the existing SELECT and
use scalar grep { !$_->{lowestPriority} } @priority instead.

This eliminates the extra DB round-trip and makes the clamping operate
on the same data snapshot that will be written back.
2026-03-21 20:28:16 +00:00
Martin Renvoize 7c74e647f8
Bug 41801: (QA follow-up) Add automated tests for FixPriority lowestPriority behaviour
Cover the four scenarios identified during QA:

- lowestPriority hold cannot be moved above non-lowestPriority holds
  (rank is clamped to just after the last non-lowest hold)
- lowestPriority hold can be reordered within the lowest-priority group
- when all holds are lowestPriority, movement is unconstrained
- when no holds are lowestPriority, FixPriority works as before

Holds are created with explicit priorities to avoid non-deterministic
tie-breaking in ORDER BY when all new holds default to priority=1.
2026-03-21 20:28:16 +00:00
Martin Renvoize 4f32ba4323
Bug 41801: (QA follow-up) Guard lowestPriority rank clamping against empty rank
When FixPriority is called with a reserve_id but no rank (rank defaults to ''),
the empty string evaluates to 0 numerically, satisfying `$rank <=
$highest_non_lowest_priority`. This caused the clamping block to unexpectedly
set rank to a positive value, which then triggered an unintended splice and
moved the hold.

Add `$rank > 0` to the guard condition so the block only fires when an explicit
positive rank was requested, matching the pre-existing semantics of the splice
block below it.
2026-03-21 20:28:15 +00:00
Martin Renvoize 8e8a5f914e
Bug 41801: (QA follow-up) Remove dead ignoreSetLowestRank param and update POD
The recursive self-call loop that ignoreSetLowestRank guarded against was
removed by the main patch. Remove the now-unused parameter from both the
function body and the POD documentation, and fix a double-word typo in the
POD while here.
2026-03-21 20:28:15 +00:00
Nick Clemens 1f089c41c1
Bug 41801: Remove recursive calls from FixPriority
This patch removes the recursive calls that set each 'lowestPriority' hold to the bottom of the priority list
and replaces it them by simply ordering the list of 'remaining' holds by lowestPriorty, then priority.

A check is also added to ensure that if a lowestPriority hold is being adjusted, it cannot be moved above holds
not marked as lowest priority.

To test:
 1 - Place many holds on a record, 50 - 150
     https://github.com/kidclamp/handy-koha-script/blob/main/randhold.pl
     This will randomly make some of the holds lowest priority
 2 - View the record in the staff interface:
     http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=312
 3 - Click on the 'Holds' tab
 4 - Adjust the priority of several holds using the dropdowns
 5 - Note the timing
 6 - Apply the patch
 7 - Adjust the priority of several holds using the dropdowns
 8 - Confirm the timing is improved
 9 - Confirm that holds adjust as expected
10 - Confirm you cannot move a lowest priority hold above a hold not marked lowest priority
11 - Confirm holds move correctly on a record where all holds are lowestPriority
12 - Confirm holds move correctly on a record where no holds are lowestPriority

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 20:28:15 +00:00
Martin Renvoize 2af497f1eb
Bug 23269: (RM follow-up) Add nonce to script tag and POD for _set_default_expirationdate 2026-03-21 20:28:15 +00:00
Johanna Räisä dd47bb82ca
Bug 23269: (follow-up) Remove form action from existing_holds
This patch removes the form action from extisting_holds form. The actions are made with REST API and the form action is not needed anymore.

Sponsored-by: Koha-Suomi Oy
2026-03-21 20:28:15 +00:00
Lucas Gass 9e77a1ee2b
Bug 23269: (RM follow-up): Tidy 2026-03-21 20:28:15 +00:00
Johanna Räisä bf0df826e9
Bug 23269: (follow-up) Fix priority modification for lesser permissions 2026-03-21 20:28:15 +00:00
Johanna Räisä 967fb23860
Bug 23269: (follow-up) Rename to item_level_holds_count and add tests
Test plan:
1. prove t/db_dependent/Koha/Holds.t

Sponsored-by: Koha-Suomi Oy
2026-03-21 20:28:15 +00:00
Johanna Räisä ee4f4f3357
Bug 23269: (follow-up) Add tests for item_level and item_id in holds API
This patch adds tests for the item_level and item_id parameters in the holds API.
It also updates the C4/Reserves.pm module to handle these parameters correctly.

Test plan:
1. prove t/db_dependent/api/v1/holds.t

Sponsored-by: Koha-Suomi Oy
2026-03-21 20:28:14 +00:00
Johanna Räisä 1d63a31dbc
Bug 23269: (follow-up) Cleanup code 2026-03-21 20:28:14 +00:00
Nick Clemens 0228c13cd1
Bug 23269: (follow-up) Check if hold item_level before grabbing barcode
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-03-21 20:28:14 +00:00
Johanna Räisä 0d595de03a
Bug 23269: (follow-up) Add spinner and fix details column
This patch adds a spinner to moving priorities and fixes the details column to show the correct information.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-03-21 20:28:14 +00:00
Johanna Räisä 18e790b79e
Bug 23269: (follow-up) Fix suspend date change
This patch fixes suspend date change, so changing the suspend will save the new suspend date.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-03-21 20:28:14 +00:00
Nick Clemens 2a6442749d
Bug 23269: (follow-up) Show lowest priority toggle unless hold is 'found'
Signed-off-by: Johanna Räisä <johanna.raisa@gmail.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-03-21 20:28:14 +00:00
Johanna Räisä b58f945270
Bug 23269: use REST API for patron_holds_table
This patch uses the REST API to populate the holds table

Test plan:
1) Apply the patch
2) Go to a record with holds
3) Check that the holds table is populated correctly
4) Check that hold tables functions work correctly
5) Change HoldsSplitQueue and HoldsSplitQueueNumbering system preference, try all variations
6) Repeat steps 3 and 4
7) Search for records and place holds to multiple records
8) Repeat steps 3 and 4
9) Try with lesser permissions

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
2026-03-21 20:28:14 +00:00
Martin Renvoize 8b626ac95c
Bug 41585: (RM follow-up) Move UseCashRegisters pref out of kohaTable script block
The tt_kohaTable_tidy.t test forbids Template::Toolkit tags in the same
<script> block as a .kohaTable() call. Bug 41585 introduced
[% IF Koha.Preference('UseCashRegisters') %] into that block.

Fix: add UseCashRegisters to addPrefs() in the TT-only script block and
replace the TT conditional with a plain JS if (prefs.UseCashRegisters) check.
2026-03-21 20:28:13 +00:00
Lucas Gass 12426b5924
Bug 37143: Do not allow required fields with only whitespace to be submitted
To test:
1. Make some fields for OPAC self registration required.
2. Create a new patron via self reg.
3. Fill out those required fields as an empty string like "  ".
4. Submit the form, it succeeds.
5. APPLY PATCH, restart_all
6. Try again. Required fields with only whitespace are treated as if they were empty.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 17:35:28 +00:00
Roman Dolny 82932c814b
Bug 41866: "Use of uninitialized value..." warning in opac-search.pl
The warning
[WARN] Use of uninitialized value in concatenation (.) or string at
/kohadevbox/koha/opac/opac-search.pl line 600.
appears in plack-opac-error.log

Test plan:
==========
1. Observe plack-opac-error.log.
2. Go to Advanced search in OPAC.
3. Select any of item type ("Limit to any of the following:") in tab
   "Item type" and perform search (do not enter any "Search for:" terms).
==> Warning appears in plack-opac-error.log.
4. Apply the patch; restart_all.
5. Repeat 2-3.
==> No warning in plack-opac-error.log.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Janusz Kaczmarek <januszop@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 17:35:28 +00:00
Owen Leonard e89b043440
Bug 41778: Fix broken display of not for loan status on item detail page
This patch makes some corrections to the way the item detail page
template defines and display an item's not for loan status.

To test you must have at least one not-for-loan item attached to a
bibliographic record.

- If necessary, edit an item to set a "Not for loan" status.
- View the bibliographic detail page for that item's record.
- Click the "Items" tab from the bibliographic detail page.
- Find the section on the page showing the item you edited.
  - You should see "Barcode XXXX" (or "Item number XXXX") along with the
    not for loan status you defined.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 17:35:28 +00:00
Martin Renvoize b38c0fb9eb
Bug 41788: DBRev 25.12.00.030
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 17:35:24 +00:00
Emily Lamancusa dee3f54f63
Bug 41788: (QA follow-up) Clarify system preference description
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 17:28:12 +00:00
Lucas Gass 810c7d5e13
Bug 41788: Implement UseHoldsQueueFilterOptions
To test:
1. Apply patch, updatedatabase, restart_all
2. Search for new system preference UseHoldsQueueFilterOptions.
3. Disable it, and navigate to Circulation -> Holds queue
4. With the pref disabled the holds queue should run with asking for filters.
5. Also try the Holds queue link in the circ-nav ( left navigation ) panel, clicking it should automatically run the holds queue.
6. Also try the link in the breadcrumbs, clicking it should automatically run the holds queue.
7. Now enabled the pref.
8. With the pref enabled navigate to Circulation -> Holds queue, the holds queue link should present filters but NOT run the holds queue.
9. Test the Holds queue link in the circ-nav ( left navigation ) panel, click it should present filters but NOT run the holds queue.
10. Also try the link in the breadcrumbs, clicking them should presnt filters but not run the holds queue.

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 17:28:08 +00:00
Lucas Gass 00c9b4a86c
Bug 41788: Add system preference UseHoldsQueueFilterOptions
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 17:28:01 +00:00
Martin Renvoize 78f7abc855
Bug 41585: Fix cash refund payouts not appearing in register transactions
This patch fixes two issues with cash refunds on the register page:

1. Payouts were not appearing in the transactions table after being
   created because accountlines were fetched before the refund
   operation completed.

2. Account credit (AC) refunds were incorrectly creating payout
   transactions when no cash should leave the register.

Changes:
- Only create payout for non-AC refund types
- Use explicit registerid from page context for payout
- Add POST/REDIRECT/GET pattern after refund to refresh page data
- Add client-side validation for cash register requirement on patron
  account page

Test plan:
1. Navigate to POS register page with transactions
2. Issue a cash refund on a transaction
3. Verify the payout appears in the transactions table
4. Issue an account credit (AC) refund
5. Verify no payout is created (only the refund credit)
6. On patron account page, attempt cash refund without register
7. Verify validation message appears

Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 17:26:44 +00:00
David Cook 0442f4ce2c
Bug 41045: Fix BZ 33363 database update
This patch fixes the BZ 33363 database update, so that it
selects uses with the "suggestions" permission rather than the "catalogue"
permission.

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 17:25:39 +00:00
Martin Renvoize 6ab8ba8d35
Bug 41615: (follow-up) Simplify 'List license' test - visit licenses directly for 500 test 2026-03-21 16:10:00 +00:00
Martin Renvoize b7f4946dff
Bug 39320: (RM follow-up) Remove shadowed getLicensesCount intercept
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 08:06:01 +00:00
Martin Renvoize bdd498af9e
Bug 41615: (RM follow-up) Fix race condition in Licenses_spec 'List license' test
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-21 07:59:41 +00:00
Martin Renvoize 7fd796d8f8
Bug 41510: (RM follow-up) Perltidy a missed file at merge time
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 15:37:20 +00:00
Martin Renvoize 090cdaf533
Bug 41834: (RM follow-up) Undo RM mistake on db update
Error between keyboard and screen.. I managed to completely replace a
db_rev with an incorrect atomicupdate move!
2026-03-20 15:25:58 +00:00
Martin Renvoize bb4407a0ff
Bug 41587: (follow-up) Clean Cypress plugin dist on rebuild to prevent stale sourcemaps
Add output.clean: true to the Cypress plugin rspack build config.

Before Bug 36674 (Nov 2025), this build emitted .map files into
t/cypress/plugins/dist/. After devtool: false was added, the map files
were no longer generated but stale ones persisted on developer machines
(dist/ is gitignored).

When the rspack update in Bug 41587 rebuilt api-client.cjs.js with
dynamic chunk loading, Cypress's webpack created a context module for
the require() expression and matched the stale .map files (webpack's
context regex matches substrings, so *.js.map matches *.js). Webpack
then failed trying to parse JSON sourcemaps as JavaScript.

output.clean: true ensures the dist directory is wiped on every build,
so stale files from previous configurations can never accumulate.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 15:16:28 +00:00
Martin Renvoize 1623f6898f
Bug 41587: (follow-up) Remove unused imports causing webpack node: URI errors
The insertData.ts spec imported query and getBasicAuthHeader directly,
pulling mysql2/promise into the browser bundle. This caused webpack to
fail with UnhandledSchemeError on node:buffer and node:diagnostics_channel
URIs introduced by updated dependencies. Both imports were unused — all
calls go through cy.task().

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 15:16:23 +00:00
Paul Derscheid 97b5c43a1a
Bug 42012: Fix missing preventDefault() call in clubs.tt
e.preventDefault was missing parentheses, so the default
action was not prevented. The element is an <a href="#">
link, so clicking it causes the page to scroll to top

To test:

- On Staff side, go to Tools > Patron Clubs
- Create a Club Template, only name is needed
- Create a Club, only name is needed
- Go to a Patron's Check out page > Clubs > **Enroll** in the club
- Back on Tools > Clubs use the Actions menu to "Search to hold"
- The page scrolls to the top before redirecting
- Apply patch
- Repeat "Search to hold"
- Page does not scroll anymore but still redirects

Signed-Off-By: Eric Phetteplace <ephetteplace@cca.edu>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 12:01:07 +00:00
Marcel de Rooy f8c838c0de
Bug 41335: Adjust toggling hold options, preselect lone item
By default hold options are collapsed. In the following cases they
are expanded:
- one of the hold dates is made mandatory via OPACMandatoryHoldDates
- an item level hold is forced for this biblio (at least one item is
  controlled by a circ rule where opacitemholds == F [Forced] )

When DisplayMultiItemHolds is enabled, item selection for specific
items is done via checkboxes; no item is preselected unless there is
only one. Otherwise an item is selected via radio buttons. The first one
is preselected.

Test plan:
Verify in steps 2 to 4 that the above rules are respected.
[1] Enable DisplayMultiItemHolds and OPACAllowHoldDateInFuture.
[2] Place hold on biblio with only one item where record and item level
    are allowed.
[3] Place hold on biblio with multiple items where item level is forced.
[4] Place hold on biblio with multiple items where item level is not
    forced.
[5] Toggle OPACMandatoryHoldDates (between no hold dates and another
    value) and repeat 2-4
[6] Disable DisplayMultiItemHolds and repeat 2-5

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:59:05 +00:00
David Cook f669f00c1c
Bug 41665: Only include Greybox in OPAC if IdRef is enabled
This patch wraps the Greybox include in the OPAC with a syspref
check on IdRef, so that it's only loaded when it's needed.

Test plan:
0. Apply the patch
1. Go to http://localhost:8080/cgi-bin/koha/opac-detail.pl?biblionumber=29
2. Search through the HTML or the Sources tab in F12 tools and note
   that Greybox assets are not loaded
3. Enable the IdRef system preference
4. Note that the Greybox assets are loaded

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:58:11 +00:00
Owen Leonard 7a5e8f2d5e
Bug 35237: (QA follow-up) Set label for attribute to first radio input
Labels with a "for" attribute should reference an existing input, so we
should keep the "Page side," "Guide box," and "Guide grid" for attribute
pointing to the first corresponding radio input in that row.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:56:52 +00:00
Harrison Hawkins af1730ada1
Bug 35237: Changed labels above checkbox on the edit layout page.
Clarified the for attribute for the original labels of the checkboxes.

Test plan:
1. Head to 'tools' then 'patron card creator'
2. Click on 'Layouts' underneath 'Manage'
3. Click on 'Edit'
4. Confirm it's easy to tell which options are available and chosen for
   page side, guide box, and guide grid
5. Inspect the page, look at and confirm the original labels have been
   renamed for page size, guide box, guide grid

Sponsored-by: Catalyst IT
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:56:44 +00:00
Courtney Brown fc9fc133b0
Bug 35237: Give patron card layout form elements unique IDs
This patch ensures the following patron card layout form fields use
unique IDs:
- page side
- guide box
- guide grid

This semantically links the options to their labels so is more
accessible for screen readers and keyboard navigation.

The patch also adds a 'default' option to the original labels so they
can become useful elements on the form rather than just text labels.

Test plan:
1. Head to 'tools' then 'patron card creator'
2. Click on 'Layouts' underneath 'Manage'
3. Click on 'Edit'
4. Confirm it's easy to tell which options are available and chosen for
   page side, guide box, and guide grid
5. Click on the text labels for the options i.e. front, back, on, off.
   Confirm the relevant radio button is selected

Sponsored-by: Catalyst IT
Signed-off-by: Noah <noah@inlibro.com>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:56:24 +00:00
Paul Derscheid 99410bc27c
Bug 41510: Handle undefined itemtype in item->effective_bookable
- Guard against undefined itemtype before calling ->bookable on it
- Return 0 (not bookable) when no itemtype is found and item-level
  bookable is not defined

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:33 +00:00
Paul Derscheid 26ffc16c38
Bug 41510: Add test for effective_bookable with missing itemtype
- Add test case for the scenario where an item has no itemtype
  because both items.itype and biblioitems.itemtype are NULL
- Note: We must set biblioitems.itemtype to NULL before creating the
  item because Koha::Item->store() automatically populates itype from
  biblioitems.itemtype when itype is not set

Test plan:
- Apply this patch only
- Run: prove t/db_dependent/Koha/Item.t :: effective_bookable
- Verify test 6 FAILS with "Can't call method 'bookable' on an
  undefined value"
- Apply the follow-up patch
- Run the test again
- Verify all tests PASS

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:33 +00:00
Tomás Cohen Arazi 573550aec7
Bug 40481: (follow-up) Simplify item loop in OPAC MARC detail
Now that the item loop only processes item fields from the filtered
$items resultset, several checks and patterns become unnecessary.

Changes:
- Remove dead checks: tag < 10 and tab != 10 guards
- Resolve $tagslib->{$item_tag} once instead of per-subfield
- Destructure ($code, $value) instead of indexed $subf[$i][0/1]
- Replace per-subfield grep over date kohafields with a hash lookup
- Remove redundant second $tagslib lookup for $kohafield
- Collapse repeatable if/else into a ternary

No functional changes.

Test plan:
1. Apply patch
2. Run the same test plan as the previous patch
=> SUCCESS: Behavior is identical
3. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:32 +00:00
Tomás Cohen Arazi 3f7c9f0f6c
Bug 40481: Fix OPAC MARC detail showing hidden items
The OPAC MARC detail view (opac-MARCdetail.pl) uses $record->fields()
to build the items table. This pulls item fields from the embedded MARC
record, bypassing the OPACHiddenItems filtering done by
filter_by_visible_in_opac.

This patch replaces the $record->fields() call with the already-filtered
$items resultset, converting each Koha::Item back to a MARC::Field via
as_marc_field.

Test plan:
1. Apply patch, do not restart plack yet
2. Setup:
   $ ktd --shell
  k$ koha-mysql kohadev <<< "UPDATE systempreferences SET value = 'damaged: [1]' WHERE variable = 'OpacHiddenItems';"
  k$ koha-mysql kohadev <<< "UPDATE items SET damaged = 1 WHERE biblionumber = 416 LIMIT 1;"
  k$ flush_memcached
3. Point your browser to the OPAC MARC detail view:
   http://kohadev.localhost/cgi-bin/koha/opac-MARCdetail.pl?biblionumber=416
=> FAIL: The damaged item is displayed in the items table
4. Restart plack:
  k$ restart_all
5. Refresh the page
=> SUCCESS: The damaged item is no longer displayed
6. Undo the test data:
  k$ koha-mysql kohadev <<< "UPDATE items SET damaged = 0 WHERE biblionumber = 416;"
  k$ koha-mysql kohadev <<< "UPDATE systempreferences SET value = '' WHERE variable = 'OpacHiddenItems';"
  k$ flush_memcached
7. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:32 +00:00
David Cook f83a2d39eb
Bug 30261: Update unit tests
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:32 +00:00
David Cook 532d10a800
Bug 30261: Render opac 404 for opac/tracklinks.pl
This patch updates C4::Output::output_error to take
an additional argument which contains an "interface"
parameter, so that opac/tracklinks.pl can generate
an OPAC error instead of an Intranet error.

Test plan:
0. Apply patch and koha-plack --restart kohadev
1. Go to http://localhost:8081/test.pl
2. Note normal Intranet 404 error
3. Go to http://localhost:8080/cgi-bin/koha/tracklinks.pl?uri=https%3A%2F%2Ftest.com&biblionumber=29
4. Note normal Opac 404 error
4b. For extra points, note that tracklinks.pl returns a 404 instead of a 302 to the 404.pl page
5. Change 'TrackClicks' syspref to "Track"
6. Go to http://localhost:8080/cgi-bin/koha/tracklinks.pl?uri=https%3A%2F%2Ftest.com&biblionumber=29
7. Note normal Opac 404 error
7b. For extra points, note that tracklinks.pl returns a 404 instead of a 302 to the 404.pl page
8. Rejoice!

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:32 +00:00
Fridolin Somers c10e7a67df
Bug 31717: Value builder unimarc_field_010.pl also use 214$c
Value builder unimarc_field_010.pl tries to find publisher from ISBN in order to fill 210$c.
Since there is now 214$c for publisher this value builder should be adapted.
214 is more important than 210 so if there are both, only 214 will be filled.

Test plan :
1) Choose an existing record with an ISBN and a published in biblioitems.publishercode,
   For example 9782070612369 and Gallimard
2) Use value builder unimarc_field_010.pl on 010$a
3) Create a new record
4) Enter 9782070612369 in 010$a
5) Click on value builder icon on this subfield
6) Check you see 'Gallimard' in 214$c

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:32 +00:00
Marcel de Rooy 911fdc330c
Bug 41699: (QA follow-up) Spelling
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:32 +00:00
Janusz Kaczmarek cb9679cfd8
Bug 41699: onsite_checkout not available in Statistics wizards
In Reports > Statistics wizards > Circulation there is no option to
extract information about entries with statistics.type =
'onsite_checkout' (it is lacking from the Type list: Checkout, Renewal,
Checkin, Local use).

Test plan
=========
1. In a fresh ktd instance set OnSiteCheckouts systempreferece to
   'Enable'.
2. Make an onsite checkout (mind the advanced checkout options icon) of
   any item to any patron from your lilbrary.
3. From Reports > Statistics wizards > Circulation check the issue
   statistics (Checkout, Local use) for the current day.
4. Note that you are unable to see the checkuot in the statistics.
5. Apply the patch; restart all.
6. You should be able to choose Onsite checkout from the Circulation
   statistics Type drop-down list and also you will now see the onsite
   checkout in statistics result.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:32 +00:00
Jonathan Druart d331c7aba8
Bug 40744: Ignore --del-exp-selfreg if pref is not set
If PatronSelfRegistration we should ignore (i.e. do nothing) if
--del-exp-selfreg is passed to cleanup_database.pl

It prevents the script to warn that the pref is not set.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:31 +00:00
David Nind e70e6a233e
Bug 42020: (follow-up) Fix spelling for button - web site to website
Changes the spelling for the button in the pop-up window (when a
library URL is defined) - "Visit web site" to "Visit website".

The common spelling now is just website.

Sponsored-by: Pontificia Università di San Tommaso d'Aquino (Angelicum)
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:31 +00:00
Janusz Kaczmarek 194a3b4a9f
Bug 42020: (Bug 39482 follow-up) Library info link shown in OPAC without OpacLibraryInfo and library URL
Prior to Bug 39482 a link to library info modal (with (i) icon) was
generated only if OpacLibraryInfo was defined (cf. original Bug
29616). Now it is there even if there is no OpacLibraryInfo at all
and no URL defined for the library. It is generated in any
circumstances.

The test on $item->holding_branch->opac_info( { lang => $lang } ) in
opac-detail.pl now returns always true. This is because
Koha::Library::opac_info returns a RS now, and not an object, as it
was before (find_best_match --> search_for_display).

This patch restores the original behavior designed in Bug 29616.

Test plan:
1. Have a standard ktd installation.
2. Make sure there is no OpacLibraryInfo defined in HTML customizations.
3. Make sure there is no URL for any library.
4. In OPAC, perform any search. On a detail page you would see a link
   to an empty modal from the Current library items column.
5. Apply the patch ; restart_all.
6. Repeat p. 4. There should be no link generated now.
7. Extent the test to follow Bug 29616 logic:

A. One with both branches.branchurl and OpacLibraryInfo defined
B. One with branches.branchurl but no OpacLibraryInfo defined
C. One with OpacLibraryInfo defined but no branches.branchurl
D. One with neither branches.branchurl nor OpacLibraryInfo defined
(neither for a specific library, nor default one)

View the bibliographic detail page in the OPAC for title(s) which have
holdings from the libraries you configured above. In the holdings table,
check the behavior of the library name in the "Current library" or "Home
library" columns *

- Case A: The library name appears as a link with an "info" icon.
  Clicking it shows the library information in a modal window. In the
  modal footer, a "Visit web site" button should take you to the correct
  URL.
- Case B: The library name appears as a link without an icon. Clicking
  the link takes you to the correct URL.
- Case C: The library name appears as a link with an "info" icon.
  Clicking it shows the library information in a modal window. In the
  modal footer there is no "Visit web site" button.
- Case D: The library name is plain text.

* Display of "Current library" and/or "Home library" is controlled by
  the OpacLocationBranchToDisplay system preference.

Sponsored-by: Pontificia Università di San Tommaso d'Aquino (Angelicum)
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:31 +00:00
Martin Renvoize 10cb20415b
Bug 41367: Add AdminMenu island and integrate into Record sources
- Replace static TT admin-menu sidebar with AdminMenu.vue island web component
  registered in the islands componentRegistry
- Fix NavigationItem to conditionally apply breadcrumb-item class via
  isBreadcrumb prop, fixing incorrect class on left-menu items
- Update record_sources.tt to use sub-header/main-container wrappers
  with aside='admin-menu', rendering the admin sidebar and breadcrumbs
- Update RecordSources/Main.vue to use Teleport for breadcrumbs into
  the sub-header container and remove self-contained layout

Test plan:
  1. Go to Administration > Catalog > Record sources
  2. Confirm the administration sidebar menu appears on the left side of
     the page, with all sections visible according to your permissions
     (System preferences, Basic parameters, Catalog, etc.)
  3. Confirm the breadcrumb trail at the top reads:
       Koha > Administration > Record sources
  4. Confirm the "Catalog" section of the sidebar is
     expanded and "Record sources" is not highlighted as current
     (it lives in the main content area, not the sidebar)
  5. Click another link in the sidebar (e.g. "System preferences") and
     confirm navigation works normally
  6. Return to Record sources and add, edit, and delete a record source
     to confirm the sidebar does not interfere with the SPA behaviour
  7. Navigate to any other admin page (e.g. Libraries, Item types) and
     confirm the sidebar still renders correctly on those pages

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:54:31 +00:00
Martin Renvoize b3357ab31b
Bug 41834: Fix GetDelimiterChoices after options column removed from sysprefs.sql
We removed the `options` column from all sysprefs.sql INSERT statements,
leaving CSVDelimiter.options as NULL in the DB. This broke GetDelimiterChoices()
which relied on that column for the list of valid delimiter choices, resulting
in empty dropdowns across all report pages and an uninitialized value warning in
the test suite.

Fix by hardcoding the choices (matching admin.pref) and reading the
current value via C4::Context->preference() instead of a raw DB query.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:53:44 +00:00
Martin Renvoize 9818b3bf8d
Bug 41834: DBRev 25.12.00.029
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:22:03 +00:00
Jonathan Druart 8864c23f3d
Bug 41834: Set pref to 0 if '' passed for YesNo
This is a bit ugly, we do that already in t/db_dependent/check_sysprefs.t
We could have this moved to a "is_boolean" method of
Koha::Config::SyspPref (LATER)

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 11:10:13 +00:00
Martin Renvoize 33f3240b54
Bug 41587: (follow-up) Fix js-yaml version conflict with resolutions override
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 09:04:21 +00:00
Paul Derscheid 787295b7f1
Bug 41587: (follow-up) Fix newly reported dependency vulnerabilities
- Bump minimatch dependency and resolution from ^3.0.2 to ^3.1.4 (HIGH)
- Bump lodash dependency and resolution from ^4.17.12 to ^4.17.23 (MODERATE)
- Add resolution for fast-xml-parser ^4.5.4 (CRITICAL)
- Add resolution for undici ^6.23.0 (MODERATE)
- Add resolution for serialize-javascript ^7.0.3 (HIGH)

Test plan:
- yarn audit --level high should show 0 vulnerabilities
- yarn build && yarn build:prod should complete without errors
- yarn cypress run should complete without (or currently expected) errors

@RM: You will need to push yarn.lock as well

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 07:58:58 +00:00
Paul Derscheid 92d2dfc4e3
Bug 41587: Fix moderate severity node dependency vulnerabilities
- Add resolution for micromatch ^4.0.8
- Add resolution for @cypress/request ^3.0.0
- Add resolution for js-yaml ^4.1.1

Test plan:
- yarn audit --level moderate should show reduced vulnerability count
- yarn build && yarn build:prod should complete without errors
- yarn cypress run should complete without (or currently expected) errors

@RM: You will need to push yarn.lock as well

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 07:58:51 +00:00
Paul Derscheid 8dd068675b
Bug 41587: Fix critical and high severity node dependency vulnerabilities
- Upgrade gulp-exec to ^5.0.0 (fixes lodash.template HIGH)
- Add resolution for form-data ^2.5.4 (CRITICAL)
- Add resolution for braces ^3.0.3 (HIGH)
- Add resolution for qs ^6.14.1 (HIGH)

Test plan:
- yarn audit --level high should show 0 vulnerabilities
- yarn build && yarn build:prod should complete without errors
- yarn cypress run should return without (or currently expected) errors

@RM: You will need to push yarn.lock as well

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 07:58:41 +00:00
David Cook e403698cd2
Bug 41834: (follow-up) nullify marcflavour in atomicupdate
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 07:57:23 +00:00
Jonathan Druart 92b8e9ea3c
Bug 41834: Fix order of underscore
In the previous tests we had:
  $b =~ s/_/ZZZ/g;    # mysql sorts underscore last, if you modify this qa-test-tools will need adjustments

But this is wrong, try:
  select variable from systempreferences;
and confirm that hide_marc is before hidelostitems and that
intranet_includes is before IntranetAddMastheadLibraryPulldown

We will deal with qa-test-tools once this is pushed.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 07:57:20 +00:00
Jonathan Druart e249392147
Bug 41834: Remove columns from sysprefs.sql
In vim `:%s/^\(('[^']*', \?'[^']*'\).*/\1),/`

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 07:57:17 +00:00
Jonathan Druart 10e1708456
Bug 41834: Set systempreferences's options, explanation and type to NULL
We currently have to set options, explanation and type in sysprefs.sql and the atomic/dbrev files.
However we (almost) never use them, we rely on the values in the yml files.

We could remove them and don't require them when when add new sysprefs.

It will avoid the mess we currently have (mismatch between new and updated installations, see bug 41682 and bug 41800).

Instead of fixing the discrepancies (again, see bug 41800) we should
NULL the 3 columns in sysprefs.sql and so at the DB level to rely only
on the yaml files.

This patch does several things:
1. Remove the 3 columns from sysprefs.sql to use the default NULL
2. Atomic update:
 * Set to NULL for existing installations as well
 * Rename UseICUStyleQUotes => UseICUStyleQuotes
 * Delete OpacMoreSearches and OPACMySummaryNote, old prefs that have been moved
 to additional contents
3. Fix some errors in yaml files (mostly empty entries)
4. Move parsing of yaml files to a dedicated Koha::Devel::Syspref module
5. Retrieve all yaml content using new methods added to Koha::Config::SysPrefs
(that will be reused in follow-up bugs)

Test plan:
1. Run updatedatabase and confirm that now all systempreferences rows in DB
have options, explanation and type set to NULL (except local-use prefs)
2. `prove t/db_dependent/check_sysprefs.t` should return green
3. Confirm that the UI is still working correctly

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 07:57:11 +00:00
Martin Renvoize b7eea37e1a
Bug 41247: (follow-up) Fix Cypress test after administration rename
Bug 41494 renamed "Koha administration" to "Administration" in the
staff interface. Update the Batches_spec.ts Cypress test to use the
new label.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 07:55:20 +00:00
Martin Renvoize 764a468ec3
Bug 42098: (follow-up) Enable plugin in ImportBatch.t test
The Handler::run change to skip disabled plugins broke the
RecordsFromMarcPlugin subtest in ImportBatch.t. The MarcFieldValues
plugin was installed but never enabled, so Handler::run returned
early. Add $plugin->enable() before calling RecordsFromMarcPlugin.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-20 07:51:01 +00:00
Jonathan Druart 5059a0b360
Bug 41989: Call getlanguage after get_template_and_user
This patch prevents the UI to randomly change the language when reaching
addbooks

The problem is that getlanguage is called BEFORE get_template_and_user,
without, and "interface" (C4::Context->interface) is not set yet.

Open C4/Languages.pm, search "sub getlanguage"
We first read the cache, then if there is no interface we reach
GetTranslatedLanguages that does return languages in a random order,
then we pick the first one
765         @languages = map { $_->{rfc4646_subtag} } @{ getTranslatedLanguages( $interface, $theme ) };
783     $language = shift @languages unless $language;

We must call getlanguage AFTER that the cache is populated.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:52:24 +00:00
Jonathan Druart 1984f205ed
Bug 39055: Adjust xt tests to handle login_op
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:51:48 +00:00
David Cook 2691d566ed
Bug 39055: Fix login condition when using op and login_op
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:51:45 +00:00
David Cook 137b41c510
Bug 39055: Add change to Koha::App::Plugin::CSRF and fix unit test
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:51:41 +00:00
David Cook 1e504eb771
Bug 39055: Add more login_op checks to middleware
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:51:38 +00:00
Jonathan Druart 9121cbd8a4
Bug 39055: Fix mojo/acquisition/vendors.t
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:51:33 +00:00
David Cook 16c8d0d9a4
Bug 39055: Change "op" to "login_op" when logging in to allow original op to flow through
This patch changes "op" to "login_op" when logging in, so that the original op can
flow through to whichever controller is processing the login and subsequent op.

Test plan:
0. apply the patch and koha-plack --restart kohadev
1. Without logging in, go to the following:
http://localhost:8081/cgi-bin/koha/reports/guided_reports.pl?id=1&op=run
2. Log in and note that you can see the report
3. Try logging into the OPAC
4. Try logging into the self-checkout
5. Create a new self-reg account and try logging in with the auto-filled details
6. Test anything else you can think to test here

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:51:27 +00:00
Pedro Amorim e838b70be7
Bug 41247: Add cypress tests
Apply just the 2 cypress tests patches, run:
cypress run --spec t/cypress/integration/ILL/Batches_spec.ts

Notice it fails.
Apply the fix patch. Repeat the command above. Tests must pass.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:45:11 +00:00
Pedro Amorim 0387ebfb97
Bug 41247: Preparation: Cypress XML plugin
Add a way to access and manipulate koha-conf.xml (we need to ensure plugins_restricted is 0 for the actual tests)

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:44:01 +00:00
Pedro Amorim b591b668d2
Bug 41247: Improve ILL batches modal resetability
Test plan, preparation:
A) Enable ILLModule
B) Install a metadata enrichment plugin, e.g:
https://github.com/openfifth/koha-plugin-api-pubmed/releases/tag/1.1.1
C) Restart plack to pick up the newly installed plugin if needed.
D) Create a new batch, visit:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?

Issue #1:
1.1) Click 'New ILL requests batch'
1.2) Enter a name, '42' on the cardnumber and any library. Click 'Continue'.
1.3) Click the 'Close' button (or click anywhere outside the modal) to close the modal.
1.4) Repeat 1.1). Notice the 'Continue' button is no longer there.

Issue #2:
2.1) Click 'New ILL requests batch'
2.2) Enter a name, '42' on the cardnumber and any library. Click 'Continue'.
2.3) Enter '123' on the textarea, click 'Process identifiers'.
2.4) Notice a 'Add items to batch' buttons appears. Don't click that, as before, close the modal.
2.5) Repeat 2.1) Notice the progress bar shows right off the bat (it shouldn't) with NaN as its value as well as table elements (export button, 'No entries to show' message, etc, with no table.

Apply patch, hard-refresh the browser and repeat Issues 1 and 2. Confirm the patch fixes them.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:43:52 +00:00
Fridolin Somers 3b4db6dba6
Bug 40306: Use GET in form of value_builder/unimarc_field_4XX.pl
Since CSRF procetion form without CUD action must use GET instead of POST
Fix value_builder/unimarc_field_4XX.pl

Test plan:
1) Use UNIMARC DB
2) Be sure value builder unimarc_field_4XX.pl is defined on 461$t
3) Create a new biblio record
4) On 461$t click on 'Tag editor' icon
5) Perform a search
6) Click on choose
7) Field 461 is filled

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:42:29 +00:00
Tomás Cohen Arazi d210db0e6b
Bug 41173: (follow-up) Don't pass params to base routes in breadcrumbs
The ERMHome route is a base route that doesn't accept params, but
NavigationItem was passing all current route params (license_id,
agreement_id, etc.) to it, causing Vue Router errors.

Fix by checking if the route is a base route (is_base flag) and
passing an empty params object in that case.

Test plan:
1. Run Cypress tests: yarn cypress run
2. Verify AdditionalFields_spec.ts now passes all 12 tests
3. Navigate to ERM module and verify breadcrumbs work correctly

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:22:16 +00:00
Jonathan Druart 4ae4efd268
Bug 41173: Add a Cypress test
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:22:14 +00:00
Tomas Cohen Arazi 5f95a00d4f
Bug 41173: Fix ERM breadcrumb causing page reload
The main ERM route was missing a 'name' property, causing the
navigation store to treat it as an external path (.pl extension).
This resulted in breadcrumb links using href instead of router-link,
causing unwanted page reloads.

To test:
1. Navigate to ERM module
2. Go to any sub-section (Agreements, Licenses, etc.)
3. Click on 'E-resource management' in the breadcrumb
=> FAIL: Notice the page reloads!
4. Apply the patch
5. Repeat steps 1-3
=> SUCCES: Verify the page doesn't reload and navigation works smoothly
6. Sign off :-D

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:22:07 +00:00
Kyle M Hall 42472f6b34
Bug 42098: EDIFACT edi_cron.pl runs disabled plugins due to bug in Koha::Plugins::Handler::run
It appears that edi_cron.pl will run disabled EDI plugins. It uses Koha::Plugins::Handler::run that does not check if the plugins is gets are disabled.

Koha::Plugins::Handler::run needs to be fixed to not run disabled plugins.

Test Plan:
1) Apply this patch
2) prove t/db_dependent/Koha/Plugins/Plugins.t

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
2026-03-19 10:20:35 +00:00
Jonathan Druart c1ab7be960
Bug 41384: Trying to fix SIP2/Accounts.ts random failures
The intercept is done too late, it must be done before we load the view,
and then we should wait for it.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-18 14:24:32 +01:00
Jonathan Druart be37389414
Bug 38262: (follow-up 2) Fix cypress tests
16:40:49       <failure message="The following error originated from your application code, not from Cypress. It was caused by an unhandled promise rejection.
16:40:49
16:40:49   &gt; response.filter is not a function
16:40:49
16:40:49 When Cypress detects uncaught errors originating from your application it will automatically fail the current test.
16:40:49
16:40:49 This behavior is configurable, and you can choose to turn this off by listening to the `uncaught:exception` event.
16:40:49
16:40:49 https://on.cypress.io/uncaught-exception-from-application" type="TypeError"><![CDATA[TypeError: The following error originated from your application code, not from Cypress. It was caused by an unhandled promise rejection.
16:40:49
16:40:49   > response.filter is not a function
16:40:49
16:41:13 When Cypress detects uncaught errors originating from your application it will automatically fail the current test.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-18 09:50:23 +01:00
Jonathan Druart a6a6fafe74
Bug 38262: Do not fail if at least one additional field exists in DB
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-18 09:50:22 +01:00
Jonathan Druart b936dc5d73
Bug 38365: Fix Cypress tests
ReferenceError: window is not defined
    at /kohadevbox/koha/t/cypress/plugins/dist/api-client.cjs.js:521:26
    at /kohadevbox/koha/t/cypress/plugins/dist/api-client.cjs.js:523:3
    at Object.<anonymous> (/kohadevbox/koha/t/cypress/plugins/dist/api-client.cjs.js:747:3)
    at Module._compile (node:internal/modules/cjs/loader:1521:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1623:10)
    at Object.require.extensions.<computed> [as .js] (/kohadevbox/Cypress/12.17.4/Cypress/resources/app/node_modules/ts-node/dist/index.js:851:20)
    at Module.load (node:internal/modules/cjs/loader:1266:32)
    at Function.Module._load (node:internal/modules/cjs/loader:1091:12)
    at Module.require (node:internal/modules/cjs/loader:1289:19)
    at require (node:internal/modules/helpers:182:18)
    at Object.<anonymous> (/kohadevbox/koha/t/cypress/plugins/api-client.js:11:23)
    at Module._compile (node:internal/modules/cjs/loader:1521:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1623:10)
    at Object.require.extensions.<computed> [as .js] (/kohadevbox/Cypress/12.17.4/Cypress/resources/app/node_modules/ts-node/dist/index.js:851:20)
    at Module.load (node:internal/modules/cjs/loader:1266:32)
    at Function.Module._load (node:internal/modules/cjs/loader:1091:12)
    at Module.require (node:internal/modules/cjs/loader:1289:19)
    at require (node:internal/modules/helpers:182:18)
    at Object.<anonymous> (/kohadevbox/koha/t/cypress/plugins/insertData.js:14:29)
    at Module._compile (node:internal/modules/cjs/loader:1521:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1623:10)
    at Object.require.extensions.<computed> [as .js] (/kohadevbox/Cypress/12.17.4/Cypress/resources/app/node_modules/ts-node/dist/index.js:851:20)
2026-03-17 09:38:51 +01:00
Jonathan Druart 1e1a446a55
Bug 38365: Fix xt/author/valid-templates.t
13:12:25 #   Failed test '/kohadevbox/koha/koha-tmpl/intranet-tmpl/prog/en/includes/str/import_from_kbart_file.inc'
13:12:25 #   at xt/author/valid-templates.t line 147.
13:12:25 # undef error - : filter not found
13:12:25
13:12:25 #   Failed test '/kohadevbox/koha/koha-tmpl/intranet-tmpl/prog/en/includes/str/job_progress.inc'
13:12:25 #   at xt/author/valid-templates.t line 147.
13:12:25 # undef error - : filter not found
13:12:25
13:12:25 #   Failed test [13:12:08] xt/author/valid-templates.t .................................................
13:12:25 Dubious, test returned 28 (wstat 7168, 0x1c00)
13:12:25 Failed 28/870 subtests
13:12:26 '/kohadevbox/koha/koha-tmpl/intranet-tmpl/prog/en/includes/str/tinymce_i18n.inc'
13:12:26 #   at xt/author/valid-templates.t line 147.
13:12:26 # undef error - : filter not found
13:12:28
13:12:28 #   Failed test '/kohadevbox/koha/koha-tmpl/intranet-tmpl/prog/en/includes/str/erm_sushi_harvester.inc'
13:12:28 #   at xt/author/valid-templates.t line 147.
13:12:28 # undef error - : filter not found
2026-03-17 09:38:50 +01:00
Jonathan Druart f124229b23
Bug 38365: Fix Koha/Middleware/ContentSecurityPolicy.t
GIT_INSTALL is not set and we get the wrong root.
The test suite does not pass on a non git install anyway.
2026-03-17 09:38:50 +01:00
David Cook edb99983f7
Bug 38365: Require a non-empty nonce in FrameworkPlugin.t
Test plan:
0. Apply the patch
1. Run the following:
prove t/db_dependent/FrameworkPlugin.t \
    t/db_dependent/Koha/Plugins/Valuebuilder_hooks.t \
    t/db_dependent/Koha/UI/Form/Builder/Biblio.t \
    t/db_dependent/Koha/UI/Form/Builder/Item.t \
    t/db_dependent/Serials.t

Squashed version of the following patches:

Bug 38365: Fix Koha/UI/Form/Builder/Biblio.t

    #   Failed test at t/db_dependent/Koha/UI/Form/Builder/Biblio.t line 216.
    #                   '<script nonce="">
    # $(document).ready(function () {
    #
    # });
    # </script>
    # '
    #     doesn't match '(?^us:<script>.*</script>)'
    # Looks like you failed 1 test of 4.

Bug 38365: Fix Koha/UI/Form/Builder/Item.t

 # There were 16 warning(s)
 #     Previous test 0 ''
 #     Use of uninitialized value $csp_nonce in concatenation (.) or string at /kohadevbox/koha/Koha/FrameworkPlugin.pm line 381.
 #  at /kohadevbox/koha/Koha/FrameworkPlugin.pm line 381.

Bug 38365: Fix Serials.t

Bug 38365: Fix FrameworkPlugin.t
2026-03-17 09:38:49 +01:00
Jonathan Druart 99cfa22537
Bug 38365: Do not rely on KOHA_HOME in tests
We never do that in our tests, and KOHA_HOME is not defined for
the kohadev-koha user in ktd.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:44 +01:00
Lari Taskula 046e0f7885
Bug 38365: Adjust t/mock_templates/opac-tmpl/bootstrap/en/modules/opac-csp.tt
This file now tidies after the patch
"Make tidy.pl compatible with script elements with attributes"

To test:
1. prove t/db_dependent/Koha/Middleware/ContentSecurityPolicy.t

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:43 +01:00
Jonathan Druart e00059dd1a
Bug 38365: Add CSP nonces to all inline script and style tags
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Amended-by: Jonathan Druart
Regenerated the patch on top of bug 41324, using:
perl misc/devel/add_csp_nonces.pl --apply
============================================================
Summary:
  Files scanned:  881
  Files modified: 456
  Tags modified:  800
============================================================

Also note that this manual change:
koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-authoritiessearchresultlist.tt
-    <script type="text/JavaScript">
+    <script nonce="[% Koha.CSPNonce | $raw %]">

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:42 +01:00
David Cook 90f173ac6a
Bug 38365: Add context to Koha::Devel::Files in add_csp_nonces.pl
The add_csp_nonces.pl file should use the exceptions in
Koha::Devel::Files.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:41 +01:00
Jonathan Druart 62f5411fb5
Bug 38365: Add test_valuebuilder_popup.tt to the exception list
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:40 +01:00
David Cook 8b17f6158d
Bug 38365: Set __webpack_nonce__ for style-loader plugin and fix fontawesome imports
By setting __webpack_nonce__ webpack/rspack will add the nonce to styles
added using style-loader plugin.

Also, stop fontawesome from auto injecting CSS elements, and instead
manually import them so that they can be picked up by style-loader

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:39 +01:00
David Cook e011f2f6e1
Bug 38365: Make tidy.pl compatible with script elements with attributes
tidy.pl will still tidy whitespace around script elements that have
attributes like "nonce"

Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:38 +01:00
David Cook 4d589b659d
Bug 38365: Fix t/db_dependent/Koha/Plugins/Valuebuilder_hooks.t
This initialises the CSP nonce in the unit test so that
t/lib/plugins/Koha/Plugin/TestValuebuilder/test_valuebuilder_popup.tt
will get its script element rewritten correctly.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:37 +01:00
Lari Taskula c42848609c
Bug 38365: Make swagger-cli happy
Schema type [array,object] is not valid in OpenAPI version 2.

There are two different CSP reporting directives.

One is the now deprecated "report-uri" directive. Browsers typically send a single
JSON object following the CSP2 specification.

"report-uri" is deprecated in CSP3 in favor of the newer "report-to" directive. It
is expected to contain an array of report objects.

There are browser that do not yet support the "report-to" directive, so both
directives should be supported.

There are also variations among the "report-uri" format. See
https://www.tollmanz.com/content-security-policy-report-samples/

Because of these variations, it becomes quite complex to handle the most common
cases in a single API endpoint.

We could build two separate endpoints for both directives.

Or we can accept any type of input and handle the variations in a single endpoint.
This patch attempts to do that.

Before this patch input parameters were validated by JSON::Validator. That is the
normal and recommended way to build OpenAPI endpoints in Koha. After this patch we
remove all input parameter restrictions from the OpenAPI spec, and simply accept
any type of body.

This means the controller will now always return a HTTP 204 regardless of the type
of data passed to the controller. It instead truncates too long input before
writing them into log files.

As this is not an endpoint that developers will build new Koha clients for, we
do not have to offer a very in-depth input parameter spec for this endpoint.

To test:
1. t/db_dependent/api/v1/public/csp_reports.t

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:37 +01:00
Lari Taskula 265d204f09
Bug 38365: (follow-up) Merge CSP middleware tests
It seems the test outside of db_dependent required presence of database after all

To test:
1. prove t/db_dependent/Koha/Middleware/ContentSecurityPolicy.t

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:36 +01:00
Jonathan Druart bba61e6bc9
Bug 38365: Use Koha::Devel::Files->ls_tt_files in add_csp_nonces.pl
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:35 +01:00
Jonathan Druart 18c0c012af
Bug 38365: Add xt test
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:34 +01:00
David Cook 38475a3e35
Bug 38365: Switch from using ENV to package level variable for nonce caching
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:33 +01:00
David Cook 9f78d81bf0
Bug 38365: Add warning that intranet/staff interface is not ready for "enabled" mode yet
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:32 +01:00
David Cook 9fa86b090b
Bug 38365: Fix csp/csp-reports tag in API spec
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:32 +01:00
David Cook 87194ea1f2
Bug 38365: Remove old usage of Koha::Cache::Memory::Lite
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:31 +01:00
David Cook e8afb55abd
Bug 38365: Replace `use strict; use warnings;` with `use Modern::Perl`
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:30 +01:00
David Cook 87d0abc690
Bug 38365: Remove old default values from koha-conf* files
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:29 +01:00
Jonathan Druart 4e5e2a342a
Bug 38365: Fix new_line_at_the_end_of_file QA failure
[FAIL] api/v1/swagger/definitions/csp_report.yaml
   FAIL   new_line_at_the_end_of_file
                Missing new line at the end of file, make sure your editor correctly reads .editorconfig ('insert_final_newline = true'). See bug 40541.

Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:28 +01:00
Lari Taskula e32af26bca
Bug 38365: (follow-up) Move default Content-Security-Policy value into Perl module, add tests
To test:
1. prove t/Koha/ContentSecurityPolicy.t

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:27 +01:00
Lari Taskula dc15cc3a04
Bug 38365: (follow-up) Only get the nonce in templates if CSP is enabled, add tests
To test:
1. prove t/db_dependent/Koha/Template/Plugin/Koha.t

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:27 +01:00
David Cook c8bfd03386
Bug 38365: Only get the nonce in templates if CSP is enabled
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:26 +01:00
David Cook da52fd9c6f
Bug 38365: Move default Content-Security-Policy value into Perl module
By defining a default policy in the Perl module, we can easily update
it over time. Using koha-conf.xml will just be for overrides, so people
can manage their own there.

Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:25 +01:00
Lari Taskula b14528e2f8
Bug 38365: Disable the endpoint when CSP is disabled in KOHA_CONF
To test:
1. prove t/db_dependent/api/v1/public/csp_reports.t

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:24 +01:00
Lari Taskula bfe0d2f2c6
Bug 38365: (QA follow-up) Fix param sample indentation
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:23 +01:00
Lari Taskula 8da256080d
Bug 38365: Restore violated-directive
The violated-directive is a historic name for the effective-directive field and
contains the same value.

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:22 +01:00
Lari Taskula 4e4eedf6ab
Bug 38365: (follow-up) Add support for both CSP report syntaxes
This patch fixes failing tests from the previous patch and adds more tests that
cover multiple violation reports in one POST request.

To test:
1. prove t/db_dependent/api/v1/public/csp_reports.t

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:22 +01:00
David Cook ffa7290fd1
Bug 38365: Add support for both CSP report syntaxes
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:21 +01:00
Lari Taskula d3d0592eb7
Bug 38365: Add application/reports+json as accepted Content-Type
report-to directive uses application/reports+json

To test:
1. prove t/db_dependent/api/v1/public/csp_reports.t

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:20 +01:00
Lari Taskula a472e6354a
Bug 38365: Set limitations on endpoint input
This patch attempts to set some sane input limits to the CSP violation report.

To test:
1. Rebuild API
redocly bundle --ext json api/v1/swagger/swagger.yaml --output api/v1/swagger/swagger_bundle.json
2. t/db_dependent/api/v1/public/csp_reports.t

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:19 +01:00
Lari Taskula b027a437c1
Bug 38365: Use report-to with Reporting-Endpoints
report-uri is deprecated, replaced by report-to and Reporting-Endpoints.

To test:
1. prove t/db_dependent/Koha/Middleware/ContentSecurityPolicy.t

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:18 +01:00
Lari Taskula a7b51e6701
Bug 38365: Add default log4perl configuration
This patch adds the default configuration to log4perl.conf.

Note we are passing a non-standard interface 'csp' to Koha::Logger. This is to
avoid log entries from propagating up to api-error.log.

To test:
1. prove t/db_dependent/api/v1/public/csp_reports.t

Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:17 +01:00
David Cook a26a1f4dc7
Bug 38365: Fix nonce caching and add to framework plugin scripts
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:17 +01:00
David Cook f80451023e
Bug 38365: Add <style to add_csp_nonces.pl to add nonce to style elements
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:16 +01:00
Martin Renvoize 4971626204
Bug 38365: Add CSP violation reporting API endpoint
Add /api/v1/public/csp-reports endpoint to receive and log
Content-Security-Policy violation reports from browsers.

When CSP is configured with a report-uri directive pointing to this
endpoint, browsers will POST violation reports whenever a CSP rule
is violated. These reports are logged using Koha's logging system
for administrator review.

Features:
- Public endpoint (no authentication required, as browsers send
  these reports automatically)
- Logs violations at WARN level with key details (violated directive,
  blocked URI, document URI, source location)
- Logs full report at DEBUG level for detailed analysis
- Accepts both application/csp-report and application/json content types

Configuration example (in koha-conf.xml csp_header_value):
  ...; report-uri /api/v1/public/csp-reports

Log4perl configuration to capture CSP violations:
  log4perl.logger.api.csp = WARN, CSP
  log4perl.appender.CSP = Log::Log4perl::Appender::File
  log4perl.appender.CSP.filename = /var/log/koha/csp-violations.log
  log4perl.appender.CSP.layout = PatternLayout
  log4perl.appender.CSP.layout.ConversionPattern = [%d] %m%n

Also updates koha-conf.xml documentation with CSP configuration guidance.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:15 +01:00
Martin Renvoize e8d11cc102
Bug 38365: Add script to insert CSP nonces into templates
Add misc/devel/add_csp_nonces.pl which automatically adds CSP nonce
attributes to inline <script> tags in Koha templates.

The script:
- Finds all .tt and .inc files in koha-tmpl/
- Adds nonce="[% Koha.CSPNonce | $raw %]" to inline scripts
- Skips external scripts (those with src= attribute)
- Skips scripts that already have nonce= attribute
- Skips third-party library files in lib/ and vendor/

Usage:
  perl misc/devel/add_csp_nonces.pl          # Dry run
  perl misc/devel/add_csp_nonces.pl --apply  # Apply changes

This script enables easy backporting of CSP nonce support to older
Koha versions by running it against their template directories.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:14 +01:00
Martin Renvoize ececcb535f
Bug 38365: Fix no-op //= operator on variable declarations
The //= (defined-or assignment) operator on a freshly declared
variable is a no-op since the variable is always undef at that
point. Replace with simple = assignment.

Affects header_name(), header_value(), and is_enabled() methods.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:13 +01:00
Martin Renvoize 70933f0843
Bug 38365: Increase CSP nonce entropy to 128+ bits
OWASP recommends CSP nonces have at least 128 bits of entropy.
The previous 10-character alphanumeric nonce provided only ~59 bits
(log2(62^10) ≈ 59.5).

Increase to 22 characters which provides ~130 bits of entropy
(log2(62^22) ≈ 130.5), exceeding the OWASP recommendation.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:12 +01:00
Martin Renvoize 47a28d62f9
Bug 38365: Integrate CSP middleware into Plack apps
Add Koha::Middleware::ContentSecurityPolicy to the middleware chain
in both app.psgi and the Debian plack.psgi template.

Without this integration, the CSP feature would not work - the
middleware was created but never activated. This adds it to:
- app.psgi (development/git install)
- debian/templates/plack.psgi (opac, intranet, and intranet_svc mounts)

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:12 +01:00
David Cook 4301202a2d
Bug 38365: Add "style-src-attr 'unsafe-inline';" to default policy
This change adds "style-src-attr 'unsafe-inline';" to the default CSP policy.

The reason is that we have a lot of inline styles throughout Koha,
and it will take a long time to clean those up. Also, the WYSIWYG/TinyMCE
editor injects inline styles, so it might not be possible to forbid
unsafe inline styles in Koha.

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:11 +01:00
Lari Taskula e750e6d1f5
Bug 38365: Add Koha::Template::Plugin::Koha::CSPNonce
This patch adds a new Template plugin method, Koha.CSPNonce, that can be used to
attach the Content Security Policy nonce into our templates.

To test:
1. prove t/Koha/Middleware/ContentSecurityPolicy.t
2. prove t/db_dependent/Koha/Template/Plugin/Koha.t

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:10 +01:00
Lari Taskula e160ed8b06
Bug 38365: Add Koha::Middleware::ContentSecurityPolicy
This patch adds a new Perl package, Koha::Middleware::ContentSecurityPolicy.

It is called for each Plack requests, and its job is to add the
Content-Security-Policy[-Report-Only] header with the configured value.

To test:
1. prove t/Koha/Middleware/ContentSecurityPolicy.t
2. prove t/db_dependent/Koha/Middleware/ContentSecurityPolicy.t

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:09 +01:00
Lari Taskula b2fcc61bda
Bug 38365: Add Koha::ContentSecurityPolicy
This patch adds a new Perl package, Koha::ContentSecurityPolicy.

It provides the following methods:

  $csp->header_name

Returns the name of the CSP header, based on koha-conf configuration.

  $csp->header_value

Returns the value of the CSP header, based on koha-conf configuration.

  $csp->is_enabled

Returns true or false, based on koha-conf configuration.

  $csp->nonce

Generates or sets, and returns the nonce. A CSP nonce is a random token that is
used both in the inline scripts and the Content-Security-Policy[-Report-Only]
response header.

To test:
1. prove t/Koha/ContentSecurityPolicy.t

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:08 +01:00
David Cook 7fcae0c823
Bug 38365: Remove unsafe CSP rules
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:07 +01:00
David Cook 65873238ce
Bug 38365: add nonce to style-src and add default csp to Debian koha-conf.xml template
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lari Taskula <lari.taskula@hypernova.fi>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:07 +01:00
Lari Taskula 3dd4cdc3f5
Bug 38365: Add content_security_policy section to koha-conf.xml
To test:
1. xmllint --noout --nonet etc/koha-conf.xml; echo $?
2. Observe 0

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: David Cook <dcook@prosentient.com.au>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:06 +01:00
Jonathan Druart 2a6316e462
Bug 41830: Fix Vendors_spec.ts
I think this happens because we select an option that didn't exist in
the dropdown, and "Sorry, there is no matching options" from select2 is
covering the discount input that is right under "Tax rates"

09:45:36     <testcase name="Vendor CRUD operations should add a vendor" time="0.000" classname="should add a vendor">
09:45:36       <failure message="Timed out retrying after 10000ms: `cy.type()` failed because this element:
09:45:36
09:45:36 `&lt;input data-v-2a08b27a=&quot;&quot; id=&quot;discount&quot; inputmode=&quot;numeric&quot; placeholder=&quot;Discount (%)&quot; size=&quot;6&quot;&gt;`
09:45:36
09:45:36 is being covered by another element:
09:45:36
09:45:36 `&lt;li class=&quot;vs__no-options&quot;&gt;Sorry, ...&lt;/li&gt;`
09:45:36
09:45:36 Fix this problem, or use {force: true} to disable error checking.
09:45:36
09:45:36 https://on.cypress.io/element-cannot-be-interacted-with" type="CypressError"><![CDATA[CypressError: Timed out retrying after 10000ms: `cy.type()` failed because this element:
09:45:36
09:45:36 `<input data-v-2a08b27a="" id="discount" inputmode="numeric" placeholder="Discount (%)" size="6">`
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:05 +01:00
Jonathan Druart 8aa600c880
Bug 41579: (follow-up) Tidy kohaTable - suggestion/suggestion.tt
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2026-03-16 11:57:04 +01:00
Jonathan Druart a1c7b7062c
Bug 41324: Adjust test for bug 38365
The script tags now contain a TT tag
  <script nonce="[% Koha.CSPNonce | $raw %]">

It's not a problem.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:48:19 -06:00
Jonathan Druart 62ca5e0eac
Bug 41324: Add a xt test
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:48:19 -06:00
Jonathan Druart 66d75e9561
Bug 41564: Fix conflict with 41798
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:48:12 -06:00
Lisette Scheer a28cbfd39e
Bug 42071: Add 'show' to options that load a single suggestion
Test plan:

Before applying the patch:
1. Go to acquisitions
2. Go to Purchase Suggestions
3. Create a purchase suggestion (for ease of testing, select Centerville for the library)
4. On the table, click on the suggestion number
5. Observe the suggestion does not populate, including the breadcrumbs, but the url does contain the suggestion number.
6. Apply the patch
7. restart_all
8. Rpeat steps 1-4
9. Observe the suggestion now populates and can be edited.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:19:46 -06:00
Jonathan Druart f2425666c9
Bug 41579: suggestions - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:19:45 -06:00
Jonathan Druart 8f7e3d85f9
Bug 41579: Tidy kohaTable - suggestion/suggestion.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:19:45 -06:00
Paul Derscheid 8d86a4c211
Bug 41573: (QA follow-up) Use var instead of const for dynamically loaded template
- patron-lists-tab.tt is loaded dynamically via jQuery .load()
- Using const for patron_id and cardnumber causes a redeclaration
  error when the tab reloads after adding/removing a patron
- Change to var which allows redeclaration in the global scope

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:19:44 -06:00
Paul Derscheid d5c93d917f
Bug 41573: (QA follow-up) Fix missing closing backtick in template literal
- The conversion from string concatenation to a template literal
  in addToList() missed the closing backtick
- This causes a syntax error breaking the add-to-list functionality
  on the patron lists tab

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:19:44 -06:00
Jonathan Druart ea59367794
Bug 41573: patron_lists - auto tidy
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:19:43 -06:00
Jonathan Druart 2aadd46d8d
Bug 41573: Tidy kohaTable - patron_lists/patron-lists-tab.tt
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:19:43 -06:00
Jonathan Druart 49ff9c7199
Bug 41573: Tidy kohaTable - patron_lists/lists.tt
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:19:42 -06:00
Jonathan Druart 15ae4719fe
Bug 41573: Tidy kohaTable - patron_lists/list.tt
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:19:35 -06:00
Lucas Gass 9346a3a722
Bug 41582: (RM follow-up) Add missing aria-hidden attribute to FA icon
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:46 -06:00
Lucas Gass 62a66d5fa1
Bug 41582: (RM follow-up) Tidy opac-detail.tt
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:46 -06:00
Jonathan Druart 024d9a18ba
Bug 41568: Tidy kohaTable - circ/branchtransfers.tt - fix conflict with 41134
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:45 -06:00
Owen Leonard 897c8c7944
Bug 41568: (follow-up) Fix minor JS error
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:45 -06:00
Jonathan Druart efb7754c8f
Bug 41568: circ - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:44 -06:00
Jonathan Druart a1d951b591
Bug 41568: Tidy kohaTable - circ/waitingreserves.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:44 -06:00
Jonathan Druart 261008fb7f
Bug 41568: Tidy kohaTable - circ/view_holdsqueue.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:43 -06:00
Jonathan Druart bc6c7b0398
Bug 41568: Tidy kohaTable - circ/transferstoreceive.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:43 -06:00
Jonathan Druart 5ab1ea2813
Bug 41568: Tidy kohaTable - circ/transfers_to_send.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:42 -06:00
Jonathan Druart a27d8ad394
Bug 41568: Tidy kohaTable - circ/returns.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:42 -06:00
Jonathan Druart b6ab1f8f84
Bug 41568: Tidy kohaTable - circ/reserverations.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:41 -06:00
Jonathan Druart ef58cbee21
Bug 41568: Tidy kohaTable - circ/pendingreserves.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:41 -06:00
Jonathan Druart 2379623013
Bug 41568: Tidy kohaTable - circ/pendingbookings.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:40 -06:00
Jonathan Druart fca228fddd
Bug 41568: Tidy kohaTable - circ/overdue.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:40 -06:00
Jonathan Druart 751b89ebaf
Bug 41568: Tidy kohaTable - circ/curbside_pickups.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:39 -06:00
Jonathan Druart 323833364d
Bug 41568: Tidy kohaTable - circ/circulation_batch_checkouts.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:39 -06:00
Jonathan Druart 1ddbe2d136
Bug 41568: Tidy kohaTable - circ/article-requests.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:38 -06:00
Jonathan Druart 10ddff6f8d
Bug 41569: (follow-up) Fix conflict with 39698
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:38 -06:00
Owen Leonard 13d10ffd2d
Bug 41569: (follow-up) Prevent variable redeclaration warnings
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:37 -06:00
Jonathan Druart aac1aaacc9
Bug 41569: clubs - auto tidy
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:37 -06:00
Jonathan Druart 940d1eea6d
Bug 41569: Fix conflict with 39698
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:36 -06:00
Jonathan Druart 7b63190947
Bug 41569: Tidy kohaTable - clubs/clubs-tab.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:36 -06:00
Jonathan Druart 0a1913116a
Bug 41569: Tidy kohaTable - clubs/patron-clubs-tab.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:35 -06:00
Paul Derscheid 844bebb17c
Bug 41580: (QA follow-up) Fix variable name mismatch in batch delete records
- The tidy declared const record_type but the code references
  recordtype (no underscore), causing a ReferenceError
- Rename to match the existing usage

To test:
- Go to Tools → Batch delete records
- Upload/select bibliographic records with checked out items
- Verify the red cross and yellow highlight appear for
  non-deletable records

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:35 -06:00
Paul Derscheid 745d176e2f
Bug 41580: (QA follow-up) Fix suspend status check in batch modify holds
- Fix typo: hold.suspened → hold.suspended, the suspension check
  never fired because the property name was misspelled
- Restore DOM update: the tidy replaced
  \$("#new_suspend_status").val("1") with a local variable assignment
  that had no effect on the submitted form data

To test:
- Go to Tools → Batch modify holds
- Load holds that are not suspended
- Set a suspend date without changing suspend status
- Submit and verify the suspend status is automatically set to 1

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:34 -06:00
Paul Derscheid b536f6ef6c
Bug 41580: (QA follow-up) Fix syntax error in holidays calendar options
- The tidy introduced a semicolon inside the flatpickr options
  object literal which is a syntax error, breaking the calendar
  widget entirely
- The syntax error also prevented the auto tidy from normalizing
  the surrounding code formatting
- Replace with comma to match object property separator syntax

To test:
- Go to Tools → Calendar
- Verify the calendar widget renders without JS errors

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:34 -06:00
Owen Leonard 5c150230a7
Bug 41580: (follow-up) More tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:33 -06:00
Owen Leonard 1a3927fe1e
Bug 41580: (follow-up) Fixes for errors
- In Cataloging -> Manage staged MARC records: the logic around showing
  the "Edit" button was not correct
- In Tools -> Batch patron modification: There was sometimes an error in
  the console about a variable not existing which can be resolved by
  moving the corresponding (and related) JS into the conditional which
  depends on the table of results existing.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:33 -06:00
Jonathan Druart 33661e7e8d
Bug 41580: tools - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:32 -06:00
Jonathan Druart 1e886e80ca
Bug 41580: Tidy kohaTable - tools/upload.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:32 -06:00
Jonathan Druart d75eee2f83
Bug 41580: Tidy kohaTable - tools/modborrowers.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:31 -06:00
Jonathan Druart bd9c9af0ed
Bug 41580: Tidy kohaTable - tools/manage-marc-import.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:31 -06:00
Jonathan Druart 433d4b22d3
Bug 41580: Tidy kohaTable - tools/inventory.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:30 -06:00
Jonathan Druart b01855b00f
Bug 41580: Tidy kohaTable - tools/holidays.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:30 -06:00
Jonathan Druart 98b17de848
Bug 41580: Tidy kohaTable - tools/batch_record_modification.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:29 -06:00
Jonathan Druart 0994f3605a
Bug 41580: Tidy kohaTable - tools/batch_modify_holds.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:29 -06:00
Jonathan Druart 50c51f3f5e
Bug 41580: Tidy kohaTable - tools/batch_delete_records.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:28 -06:00
Jonathan Druart 6239aa298a
Bug 41572: Fix conflict with bug 41457
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:28 -06:00
Owen Leonard f5b808e3f4
Bug 41572: (follow-up) More tidying
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:27 -06:00
Owen Leonard b6875d8d97
Bug 41572: (follow-up) Fixing two minor syntax errors
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:27 -06:00
Jonathan Druart c4a0d280a8
Bug 41572: members - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:26 -06:00
Jonathan Druart 5ae304e184
Bug 41572: Tidy kohaTable - members/readingrec.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:26 -06:00
Jonathan Druart ef89384be6
Bug 41572: Tidy kohaTable - members/pay.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:25 -06:00
Jonathan Druart 648b0d71a0
Bug 41572: Tidy kohaTable - members/moremember-print.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:25 -06:00
Jonathan Druart 4f22000d9a
Bug 41572: Tidy kohaTable - members/holdshistory.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:24 -06:00
Jonathan Druart 707f472d29
Bug 41572: Tidy kohaTable - members/boraccount.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:24 -06:00
Paul Derscheid 0fe472c1e0
Bug 41582: (QA follow-up) Fix missing prefs prefix for OPACLocalCoverImages
- The shelf browser previous-navigation AJAX callback references
  OPACLocalCoverImages without the prefs. prefix, causing a
  ReferenceError at runtime
- The next-navigation callback correctly uses prefs.OPACLocalCoverImages

To test:
- Enable OPACLocalCoverImages system preference
- Open an OPAC detail page with the shelf browser enabled
- Click the previous arrow in the shelf browser
- Verify local cover images load without JS errors

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:23 -06:00
Paul Derscheid e3f99585d5
Bug 41582: (QA follow-up) Restore missing components case in defaulttab switch
- The tidy dropped the components case from the TT SWITCH/CASE
  conversion to JS switch, causing records with defaulttab set to
  components to fall through to the default holdings tab

To test:
- Open an OPAC detail page for a record with components
- Verify the components tab is selected by default

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:23 -06:00
Paul Derscheid acf4a37ac5
Bug 41582: (QA follow-up) Fix syntax errors in defaulttab switch
- Missing colon on case 'media' causes a hard JS SyntaxError
  breaking the entire script block on page load
- Add missing semicolons after break statements

To test:
- Open an OPAC detail page for a record with media tab
- Verify no JS errors in the console
- Verify the correct default tab is selected

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:22 -06:00
Jonathan Druart b7e6e521ad
Bug 41582: Fix typo
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:22 -06:00
Owen Leonard 2892bdcea8
Bug 41582: (follow-up) Fix some syntax errors and variable collisions
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:21 -06:00
Jonathan Druart 271cf73bf6
Bug 41582: opac - auto tidy
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:21 -06:00
Jonathan Druart 3acf78df27
Bug 41582: Tidy kohaTable - sco/sco-main.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:20 -06:00
Jonathan Druart c825a7fbad
Bug 41582: Tidy kohaTable - opac-user
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:19 -06:00
Jonathan Druart e1d0092a83
Bug 41582: Tidy kohaTable - opac-suggestions.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:19 -06:00
Jonathan Druart c912c98c24
Bug 41582: Tidy kohaTable - opac-readingrecord.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:18 -06:00
Jonathan Druart 56ce976f76
Bug 41582: Tidy kohaTable - opac-messaging.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:18 -06:00
Jonathan Druart 922bad7798
Bug 41582: Tidy kohaTable - opac-illrequests.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:17 -06:00
Jonathan Druart e5020061a6
Bug 41582: Tidy kohaTable - opac-full-serial-issues.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:17 -06:00
Jonathan Druart aa8ec9e4d4
Bug 41582: Tidy kohaTable - opac-detail.tt - Novelist
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:16 -06:00
Jonathan Druart a6009fbd0d
Bug 41582: Tidy kohaTable - opac-detail.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:16 -06:00
Jonathan Druart 3bd3bf5d80
Bug 41582: Tidy kohaTable - opac-curbside-pickups.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:15 -06:00
Jonathan Druart 7fdad8b299
Bug 41582: Tidy kohaTable - opac-course-reserves.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:15 -06:00
Jonathan Druart 16b68a516b
Bug 41582: Tidy kohaTable - opac-course-details.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:14 -06:00
Jonathan Druart a4cb4d62a7
Bug 41582: Tidy kohaTable - opac-basket.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:14 -06:00
Jonathan Druart 4502e22172
Bug 41582: Tidy kohaTable - opac-account.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:13 -06:00
Owen Leonard 2da8719491
Bug 41566: (follow-up) More tidying required for itemsearch.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:13 -06:00
Paul Derscheid 7e423baea3
Bug 41566: (follow-up) Prevent runtime error by assigning fallback falsy value to ClaimReturnedLostValue
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:12 -06:00
Jonathan Druart 898c823b5a
Bug 41566: catalogue - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:12 -06:00
Jonathan Druart 163de2b82d
Bug 41566: Tidy kohaTable - catalogue_detail.inc|catalogue/detail.tt
This is a non-trivial one.

* SpineLabelShowPrintOnBibDetails is removed from the controller and we
  fetch it from the Koha.Preference TT plugin (because it is how it
  should be)
* A lot of JS variable instantiations are moved in a dedicated script
  tag: they all use TT plugins
* We are using stores to permissions and sysprefs

That's a lot of changes but nothing complicated, just a move from TT
vars to JS vars

To test this you will need to fully test the holdings tables.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:11 -06:00
Jonathan Druart 802e2dc5b6
Bug 41566: Tidy kohaTable - catalogue/itemsearch.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:11 -06:00
Jonathan Druart c3922ca531
Bug 41566: Tidy kohaTable - catalogue/issuehistory
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:10 -06:00
Paul Derscheid 8fa736ea6e
Bug 41797: (QA follow-up) Fix empty results truthiness check
- The tidy converted a TT compile-time [% IF results %] to a
  runtime JS check on original_results
- original_results is always an array (fallback to []) and an
  empty array is truthy in JS, so the chart click handler was
  always registered even with no results
- Check .length instead to match the original server-side behavior

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:10 -06:00
Jonathan Druart c4927d90b7
Bug 41797: reports/guided_reports_start - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:09 -06:00
Jonathan Druart b93f5f6723
Bug 41797: Tidy kohaTable - reports/guided_reports_start
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:09 -06:00
Jonathan Druart 69f103d14a
Bug 41581: virtualshelves - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:08 -06:00
Jonathan Druart a3477589a9
Bug 41581: Tidy kohaTable - virtualshelves/shelves.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:08 -06:00
Owen Leonard c04a136c86
Bug 41578: (follow-up) More tidying
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:07 -06:00
Owen Leonard 89873a7ba4
Bug 41578: (follow-up) Fix syntax error
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:07 -06:00
Jonathan Druart 5b8cd03010
Bug 41578: serials - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:06 -06:00
Jonathan Druart 97442056fd
Bug 41578: Tidy kohaTable - serials/subscription-numberpatterns.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:06 -06:00
Jonathan Druart cd80b1b57c
Bug 41578: Tidy kohaTable - serials/subscription-detail.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:05 -06:00
Jonathan Druart f31a271b95
Bug 41578: Tidy kohaTable - serials/serials-search.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:05 -06:00
Jonathan Druart 7458f26308
Bug 41578: Tidy kohaTable - serials/serials-collection.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:04 -06:00
Jonathan Druart 2f5cb3182b
Bug 41578: Tidy kohaTable - serials/claims.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:04 -06:00
Jonathan Druart ec7e97f2eb
Bug 41577: reserves - auto tidy
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:03 -06:00
Jonathan Druart b3cd2c2a67
Bug 41577: Tidy kohaTable - reserve/request.tt
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:03 -06:00
Jonathan Druart 40727991f2
Bug 41576: reports - auto tidy
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:02 -06:00
Jonathan Druart fb8c0bf4a6
Bug 41576: Tidy kohaTable - reports/orders_by_budget.tt
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:02 -06:00
Jonathan Druart 722e2da91a
Bug 41576: Tidy kohaTable - reports/itemslost.tt
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:01 -06:00
Jonathan Druart 88624bf752
Bug 41576: Tidy kohaTable - reports/catalogue_stats.tt
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:01 -06:00
Paul Derscheid a865d2b844
Bug 41575: (QA follow-up) Fix register description interpolation in cashup modal
- The tidy replaced TT interpolation with a JS template literal
  expression but left it inside single quotes instead of using
  backticks or string concatenation
- This caused the cashup confirmation dialog to show "Confirm cashup
  of" with no register name
- The incorrect syntax also silently swallowed quote formatting that
  the auto tidy would normally have applied
- Use string concatenation to match the surrounding code style

Test plan:
- Go to Point of sale, make a sale
- Navigate to Cash summary
- Click "Record cashup" on a register row
- Verify the dialog says "Confirm cashup of <register name>"
  instead of "Confirm cashup of"

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:00 -06:00
Jonathan Druart 3c9927af5b
Bug 41575: pos - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:03:00 -06:00
Jonathan Druart 0508e35c12
Bug 41575: Tidy kohaTable - pos/registers.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:59 -06:00
Jonathan Druart e708e4ea0f
Bug 41575: Tidy kohaTable - pos/register.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:59 -06:00
Jonathan Druart 4420daf91e
Bug 41575: Tidy kohaTable - pos/pay.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:58 -06:00
Paul Derscheid 78744f7008
Bug 41574: (QA follow-up) Rename shadowing batch_id variables
- The top-level const batch_id (from TT) is shadowed by var
  batch_id inside the .export and #savedesc click handlers
- Rename to export_batch_id and save_batch_id respectively
  to make the intent clear

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:58 -06:00
Owen Leonard 14167ad785
Bug 41574: (follow-up) More tidying
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:57 -06:00
Owen Leonard 5c445707b7
Bug 41574: (follow-up) Fixing syntax error and wrong variable name
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:57 -06:00
Jonathan Druart 7af54a2ead
Bug 41574: patroncards - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:56 -06:00
Jonathan Druart 2fc565d85c
Bug 41574: Tidy kohaTable - patroncards/edit-batch.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:56 -06:00
Paul Derscheid ca2bf33afb
Bug 41571: (QA follow-up) Rename shadowing batch_id variables
- The top-level const batch_id (from TT) is shadowed by var
  batch_id inside the .export and #savedesc click handlers
- This works correctly now but is confusing and error-prone
  if the code is later refactored
- Rename to export_batch_id and save_batch_id respectively
  to make the intent clear

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:55 -06:00
Owen Leonard 525a41f659
Bug 41571: labels - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:55 -06:00
Owen Leonard 5e864d613e
Bug 41571: (follow-up) Remove unused function
The selected_layout function is defined but never called.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:54 -06:00
Jonathan Druart a158a2fcc7
Bug 41571: Tidy kohaTable - labels/label-edit-batch.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:54 -06:00
Owen Leonard bf0bd81a98
Bug 41570: (follow-up) i18n correction, course deletion fix
The template's i18n function, I18N.tnx, doesn't work with JavaScript
variables, so I've updated it to use the template variable.

I also found that I couldn't properly test the course deletion function
without fixing a problem which exists in main: The delete course button
didn't trigger the confirmation, and the event which was supposed to
trigger the confirmation was attached to the whole form.

The confirmation is now triggered on form submit.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:53 -06:00
Jonathan Druart ab549cfd13
Bug 41570: course_reserves - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:53 -06:00
Jonathan Druart d8436460c3
Bug 41570: Tidy kohaTable - course_reserves/course-reserves.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:52 -06:00
Jonathan Druart bc1b3d5a7d
Bug 41570: Tidy kohaTable - course_reserves/course-details.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:52 -06:00
Jonathan Druart 1c3afb52ab
Bug 41567: cataloguing - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:51 -06:00
Jonathan Druart 97d2ba5c9d
Bug 41567: Tidy kohaTable - cataloguing/z3950_search.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:51 -06:00
Jonathan Druart 282f649fa0
Bug 41567: Tidy kohaTable - cataloguing/concerns.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:50 -06:00
Jonathan Druart 08937eca56
Bug 41567: Tidy kohaTable - cataloguing/addbooks.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:50 -06:00
Nick Clemens d69d218d68
Bug 42068: Bug 26993 (follow-up) Don't use FOREIGN_KEY_CHECKS
To test:
1 - Checkout 25.11.x - reset_all
2 - Enable StoreLastBorrower
3 - Issue an item
4 - Ensure item has a row in items_last_borrower
    SELECT * FROM items_last_borrower
5 - Checkout main
6 - updatedatabase
7 - Try to delete any item, doesn't have to be the one you issued
    DELETE FROM items WHERE itemnumber=93;
8 - ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`koha_kohadev`.`items_last_borrower`, CONSTRAINT `items_last_borrower_ibfk_1` FOREIGN KEY (`itemnumber`) REFERENCES `items` (`itemnumber`) ON DELETE CASCADE ON UPDATE CASCADE)

9 - repeat 1-5
10 - APPLY PATCH
11 - updatedatabase
12 - Try to delete any item
13 - Success!
14 - Delete the item you issued previously (in the staff interface on via mysql)
15 - Confirm no rows in items_last_borrower

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-12 15:02:42 -06:00
Jonathan Druart ce09a18d56
Bug 41837: Remove $path from C4::Templates
commit 571d609ccd
Date:   Tue Dec 2 22:13:25 2003 +0000
    fix for bug 600

modified: C4/Output.pm
-my $path = C4::Context->config('includes') ||
-   "/usr/local/www/hdl/htdocs/includes";
+#FIXME: this is a quick fix to stop rc1 installing broken
+#Still trying to figure out the correct fix.
+my $path = C4::Context->config('intrahtdocs')."/intranet-tmpl/default/en/includes/";

This FIXME got moved to C4/Templates.pm:
commit 6b8be20497
Date:   Thu Aug 25 15:01:19 2011 +0200
    Bug 6755 Problems with switching languages

I think we are ready for "rc1" and we should remove this $path, that is actually not used in C4::Templates... sigh...

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-09 17:06:50 -06:00
Baptiste Wojtkowski c48aef304f
Bug 41330: Escape braces in serials number management
Do not apply patches before
1 - Create a new subscription to a serial
2 - Go to 'Serial Collection' panel and click on edit serials button
3 - Try and edit the serial number to "{1}" -> error 500
4 - Apply only unit tests patch and run `prove t/Serials/ModSerialStatus.t` -> Tests will
  not pass
5 - Apply patch and proceed again to 2&3 -> you will get no error
6 - run `prove t/Serials/ModSerialStatus.t` -> tests will pass

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-09 17:06:50 -06:00
Baptiste Wojtkowski 4fd0ba028c
Bug 41330: Unit tests
Do not apply patches before
1 - Create a new subscription to a serial
2 - Go to 'Serial Collection' panel and click on edit serials button
3 - Try and edit the serial number to "{1}" -> error 500
4 - Apply only unit tests patch and run `prove t/Serials/ModSerialStatus.t` -> Tests will
  not pass
5 - Apply patch and proceed again to 2&3 -> you will get no error
6 - run `prove t/Serials/ModSerialStatus.t` -> tests will pass

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-09 17:06:49 -06:00
Petro Vashchuk b6fea1c1ab
Bug 41338: Hold found notice should show item's home library and where it is currently checked in
This patch adds a description of item's home library and the library
where the item is currently checked in at, to the hold found notice when
you check in the item. Also to avoid redundancy, the home library will
be shown only when destination branch is different from the home branch.

To reproduce:
1. Configure three libraries, A, B and C.
2. Create an item with the home library A.
3. Check in the item at the library B.
4. Place a hold with a pickup branch C and then check thought the
   interface for the same library C.
5. The only information about branch related to the item will be
   "Transfer to: Fairfield".
6. Click ignore and cancel the transfer.
7. Apply the patch.
8. Check in the item again.
9. This time you should see the full information about home library and
   where the item is currently checked in at.

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-09 17:06:49 -06:00
Paul Derscheid 43aee2785e
Bug 42014: (follow-up) Only show empty state when patron is not on any list
- The empty state message was shown when available_lists was empty
  but the patron was already on all existing lists
- Add !in_lists guard so the message only displays when the patron
  is truly not on any list

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-09 17:06:48 -06:00
Paul Derscheid 02128e6570
Bug 42014: Show empty state message on patron lists tab
- When no patron lists exist in the system, the patron lists
  tab on the patron detail page renders completely blank
- Other tabs follow the convention of showing a message in
  the empty state (e.g. "Patron has nothing on hold.")
- Add an empty state message with a link to create a new
  patron list, matching the established pattern

To test:
- Go to a patron detail page and click the "Patron lists" tab
- If no patron lists exist, observe the tab is blank
- Apply patch
- Repeat the above steps
- Verify the tab now shows "Patron is not on any list." with
  a link to create a new patron list
- Click the link and create a patron list
- Return to the patron detail page and verify the "Add patron
  to list" dropdown now appears instead

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-09 17:06:48 -06:00
Martin Renvoize 4ce8081f07
Bug 29923: Prevent overpayment credit when written-off accruing fine is adjusted on check-in
When a staff member writes off an accruing fine and the item is
subsequently checked in (backdated or in fine-free mode), the fine
amount is recalculated downward via adjust(). The previous code
unconditionally created an OVERPAYMENT credit whenever the adjusted
outstanding went negative, regardless of whether the existing credits
were reversible payments or non-reversible writeoffs/discounts.

This caused patrons to receive an erroneous credit equal to the
written-off amount, since writeoffs should simply be absorbed when
the underlying fine shrinks - no refund is due.

The fix calculates the total non-reversible credits (writeoffs,
discounts, cancellations) applied to the debit using the existing
filter_by_non_reversible method, and only generates an OVERPAYMENT
credit for the portion attributable to reversible cash payments that
exceed the recalculated fine amount.

Test plan:
1. Write off an accruing fine in full
2. Check the item in (backdated or fine-free)
3. Confirm no overpayment credit is generated for the patron

Signed-off-by: Benjamin Daeuber <BDaeuber@fargolibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-09 17:06:47 -06:00
Jake Deery 46fb776762
Bug 41976: LinkWrapper no longer mangles links
This patch fixes a bug where, context-dependent, LinkWrapper can keep
referencing back to links that should be out of scope.

This was causing odd behaviour whilst writing Bug 14962 - namely, links
returning as the same value in a table of multiple rows due to
LinkWrapper treating linkData.href as one and the same for each row.

TO TEST:
== APPLY PATCH ==
a)  enable ERMModule syspref, go to /cgi-bin/koha/erm/erm.pl
b)  notice all links and buttons still work as expected
c)  go to /cgi-bin/koha/sip2/sip2.pl
d)  notice all links and buttons still work as expected
e)  go to /cgi-bin/koha/acquisition/vendors
f)  notice all links and buttons still work as expected
g)  enable PreservationModule syspref, go to
/cgi-bin/koha/preservation/home.pl
h)  notice all links and buttons still work as expected
== SIGN OFF ==

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-09 17:06:47 -06:00
Lucas Gass 3229f8a3e6
Bug 41409: DBIC changes
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-09 17:06:46 -06:00
Jonathan Druart e7855c0c01
Bug 41409: Add COMMENT to kohastructure
To fix misc4dev/run_tests.pl --run-db-compare-only

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 12:56:26 -07:00
Baptiste Wojtkowski 233e02fc1c
Bug 39725: Remove GetSuggestionByStatus from C4/Suggestions.pm
Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:05 -07:00
Baptiste Wojtkowski 452089fc00
Bug 39724: (follow-up) use the suggestion object in neworderempty.tt
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:05 -07:00
Baptiste Wojtkowski e4f14b3b1d
Bug 39724: Remove GetSuggestionInfo from C4/Suggestions.pm
Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:04 -07:00
Baptiste Wojtkowski 713b525d1f
Bug 39723: (follow-up) only use the suggestion object in template params
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:04 -07:00
Baptiste Wojtkowski df21bc24eb
Bug 39723: Remove GetSuggestionInfoFromBiblionumber from C4/Suggestions.pm
To test:
 Create, edit and receive a command from a suggestion (the lonely call
of modReceiveOrder is at the receipt in acqui/finishreceive.pl)
Run `prove prove t/db_dependent/Acquisition.t`
Run `prove prove t/db_dependent/Suggestions.t`

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:03 -07:00
Emmi Takkinen 5323b8858e
Bug 36920: Tidy file
Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:03 -07:00
Katariina Pohto 597866b9d5
Bug 36920: Greater/less than search option for item search page field drop-down menu
The field drop-down menu (including custom fields) can currently only
use operators 'is' (LIKE) and 'is not' (NOT LIKE). Greater and less than
operators would be useful especially for date fields. This patch adds
'>' and '<' operators to the search.

1. Go to item search page and navigate to the field drop-down menu
   (default value Barcode).
2. Note that the operator choices for this search menu are 'is' and 'is
   not'.
3. Apply patch and refresh cache.
4. Navigate back to the menu and note that the operator menu now
   includes '<' and '>'.
5. Choose one of the new values and refresh page to see that the
   selection remains.
6. Test a field or several with the new operators to see that the new
   operators are working as intended.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:02 -07:00
Owen Leonard 713cae3a49
Bug 41126: Move shelving location into a separate column in inventory
This patch updates the inventory template so that items' shelving
location is showin in its own column in the table of results. This makes
it consisten with other displays like the bibliographic detail holdings
table and the holds queue report.

To test, apply the patch and restart services.

- If necessary, modify a few items to add a shelving location.
- Go to Cataloging -> Inventory
- To narrow down the results, I used the item call number fields under
  "Item location filters."
- Submit the form.
- In the list of results, confirm that there is a separate column for
  shelving location and that the correct information is displayed.
- Confirm that the column visibility menu works to show and hide the
  column.
- Go to Administration -> Table settings -> Tools (sic) -> Inventory
- Confirm that there is a checkbox for the shelving location column and
  that your changes to the column's configuration are reflected in the
  inventory results.
  - Because of DataTables state saving feature you might have to clear
    your browser's local storage in order to see the new column
    configuration.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:02 -07:00
David Cook b53496e069
Bug 41986: Add more names to Contact information and clarify preferred name
This patch adds the "Middle name" to the Contact information on moremember.pl,
and it adds "Preferred name:" when it differs from the "First name:". This
provides maximum clarity to staff users.

Test plan:
0. Apply the patch
1. Go to the "Patrons" module and search for or add a new patron
2. Add a firstname, a middle name, and a surname. Also add a "Preferred name"
which matches the firstname.
3. Go to "Details" on the sidebar, and note that "First name", "Middle name",
and "Surname" appear in the "Contact information".
4. Edit the patron and change "Preferred name" to a name that does not
match the "First name"
5. Go to "Details" on the sidebar, and note that "Preferred name" now shows
as well.
6. Enjoy fully comprehending which name is which for the patron!

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:01 -07:00
David Nind 84a774d4bc
Bug 41958: Rename BibTeX cart and list download options in the staff interface
This patch renames BibTex to BibTeX (with a capital X) for the staff interface
cart and list download options, to match the OPAC.

Test plan:
1. Log in to the staff interface and search for a term that returns
   results.
2. Add some records from the results to the cart (select records >
   Add to cart).
3. Add some records from the results to a new public list (select
   records > Add to list > New list > name the list > make it a
   public list > Save).
4. Note the download options for the cart and the spelling: Cart >
   Download > BibTexi (with a lowercase X).
5. Note the download options for lists and the spelling: Lists >
   Public lists > select the list > Download list > BibTex (with a
   lowercase X).
6. Go to the OPAC and search for a term that returns results.
7. Add some records from the results to the cart (select records >
   Add to cart).
8. Note the download options for the cart and the spelling (with a
   capital X): Cart > Download > BibTeX.
9. Note the download options for lists and the spelling (with a
   capital X): Lists > Public lists > the name of your list >
   Download > BibTeX.
10. Apply the patch.
11. Recheck the download options in steps 4 and 5.
12. The option should now show as BibTeX (with a capital X).

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:01 -07:00
Owen Leonard 199cdbd34a
Bug 41807: Fix automatic tab selection on basket groups page
This patch fixes the JS in the basket groups template which is intended
to select the "Closed" tab when a "listclosed" parameter is passed.

To test, apply the patch and go to Acquisitions.

- Search for and view a vendor.
- Click "Basket groups" in the sidebar.
- Create a new basket group if necessary.
- Click "Delete" to remove the basket group.
- When the page reloads the "Closed" tab should be selected.
- Create a new basket group if necessary and click "Close and export as
  PDF." When the page reloads the "Closed" tab should be selected.
- Click "Reopen." The "Closed" tab should again be selected.

The patch doesn't make any changes to existing behavior but I think we
should re-evaluate whether the "Closed" tab is the right place to go
after deleting or reopening a basket group.

Sponsored-by: Athens County Public Libraries

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:00 -07:00
Hammat Wele 7f38682184
Bug 41692: "See all charges" link in the guarantor details does not activate Guarantees charges tab
When a member has guarantees and the guarantees have charges/fines, the
link "See all charges." in the guarantor details does not work.
It should activate the "Guarantees’ charges" tab and display the
guarantee’s charges.

Steps to Reproduce

1. Find or create a patron with the 'Kid' category.
2. Add a guarantor for this patron.
3. Go to Accounting and create a manual invoice without paying it.
4. Open the guarantor details page.
5. Click on "See all charges."
   ==> it does nothing.
6. Apply the patch
7. Repeat step 4, 5
   ==> The "Guarantees’ charges" tab is activate

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:34:00 -07:00
Brendan Lawlor ecd2f81c75
Bug 21555: Disable merge patrons button when using select all
This patch disables the merge patrons button when select all is clicked.
The button is re-enabled after clicking clear all or manually changing the selection.

To test:
1. Do a patron search
2. Select at least 2 patrons
3. The merge patron button is enabled
4. Click select all
5. The merge patron button is still enabled
6. Apply patch and refresh browser
7. Click select alll
8. The merge patron button is not enabled anymore
9. Change the selection manually
10. Confirm the merge patron button is re-enabled

Sponsored-by: CLAMS
Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:59 -07:00
Kevin Carnes d96183d41b
Bug 41496: Update current URL for item search
The "Copy shareable link" button on the item search table creates a link
based on the current URL, but an item search does not update the current URL
with the search parameters.

This patch copies the code to generate the URL with search parameters and
uses it to update the current URL.

Test plan:
1. Click "Item search".
2. Perform an item search (e.g. Home library is Centerville).
3. Click the "Copy shareable link" button on the right side of the screen
   above the table.
4. Paste and visit the link.
5. Observe that you only see the search page without any results.
6. Apply patch.
7. Repeat steps 1-4.
8. Observe that you see the results with the table settings.
9. Sign off.

Sponsored-by: Lund University Library
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:59 -07:00
David Cook fccbf3099d
Bug 41970: Fix typo in pa_class in template
This change fixes a typo from pa_loo to pa_class, so that
the ID attribute of the fieldset is properly set when using PA_CLASS
to group patron attributes.

Test plan:
0. Set up two PA_CLASS values
1. Set up two patron attributes which are viewable/editable on the
OPAC and assign them one class each
2. Go to http://localhost:8080/cgi-bin/koha/opac-memberentry.pl
3. Look at the HTML and note that fieldsets for both PA classes
have the ID of "aai_"

4. Apply the patch

5. Reload the page http://localhost:8080/cgi-bin/koha/opac-memberentry.pl
6. Note that now the fieldsets have ID attributes of
"aai_<PA_CLASS code>".
7. Rejoice as now it's easier to use Javascript and CSS and have valid
HTML!

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:58 -07:00
David Nind 537435659f
Bug 41885: (follow-up) Rename iso2709 to MARC for list downloads
Rename iso2709 to MARC in the list of download options for
lists in the staff interface.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:58 -07:00
Katrin Fischer 113d290ace
Bug 41885: Rename list download option in staff interface to 'MARC'
This makes the name match the other places in staff interface and OPAC
where we allow download of MARC records (OPAC lists and cart, staff cart).

To test:
* Create a list in staff with at least one record in it
* Check the download options offer 'iso2709'
* Apply patch
* Verify the name of the option has changed to 'MARC'
* Verify the same name is used for the download options of the list
  in the OPAC

Signed-off-by: Gretchen Maxeiner <maxeinergl@gcc.edu>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:57 -07:00
Roman Dolny 1058fe254c
Bug 41715: Argument "YYYY-MM-DD" isn't numeric in numeric lt (<)... warnings in issues_stats.pl
The warnings:
[WARN] Argument "YYYY-MM-DD" isn't numeric in numeric lt (<)
    at /kohadevbox/koha/reports/issues_stats.pl line 224.
appears in the plack-intranet-error.log.
It happens because a numerical comparison was used to compare the dates
instead of a string comparison.

To test:
========
1. Observe plack-intranet-error.log.
2. In staff interface go to Reports > Statistics wizards > Circulation.
3. Select date "From" and date "To" then submit.
4. Two warnings appear in plack-intranet-error.log.
5. Apply the patch; restart_all.
6. Repeat 2-3. No warnings appear in log.

Sponsored-by: Ignatianum University in Cracow
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-03-05 08:33:57 -07:00
Owen Leonard 9dee981687
Bug 41838: Fix automatic tab selection on MARC subfield edit pages
This patch fixes automatic tab selection on MARC subfield edit pages for
both authority and bibliographic records. The patch also removes some
unused markup and JS associated with old ways of handling tabs.

To test, apply the patch and go to Administration -> Authority types.

- Click the "Actions" button for one of the authority types and select
  "MARC structure"
- Pick a tag which has multiple subfields, e.g. 020 and click Actions ->
  Subfields.
- Click one of the subfield links (not the first one). On the subfield
  edit page the corresponding tab should be selected when the page
  loads.
- Go back and click the "Edit" button and confirm that this also
  selects the correct tab.
- Go to Administration -> MARC bibliographic framework -> MARC
  structure -> View subfields and perform the same tests.

Sponsored-by: Athens County Public Libraries
Signed-off-by: John Doe <you@example.com>
Signed-off-by: Nic Olsson <olsson.nic@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:56 -07:00
David Cook 9b2f2fcf6f
Bug 41645: Make self-checkout use responsive CSS
This change makes the responsive CSS work with self-checkout.
This changes also includes a few minor HTML changes to make the
display look good both on large and small screens.

Test plan:
0. Apply the patch
1. Go to http://localhost:8080/cgi-bin/koha/sco/sco-main.pl
2. Log in as the koha user
3. Check out barcodes 39999000004571 and 39999000001310
4. In an incognito window, add holds and charges for the
koha user
5. Go back to http://localhost:8080/cgi-bin/koha/sco/sco-main.pl
and log in as the koha user
6. Using your F12 dev tools, look at the interface with different
widths
7. Note that the datatables have a responsive design which looks
good on a mobile view
8. Note that the page stays contained to the small screen size
and you don't have to scroll horizontally to see all the content

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:56 -07:00
Roman Dolny 28b479d114
Bug 41904: "Use of uninitialized value..." warning in del_message.pl
The warning
[WARN] Use of uninitialized value in string eq at
/kohadevbox/koha/circ/del_message.pl line 54.
appears in plack-intranet-error.log

Test plan:
==========
1. Observe plack-intranet-error.log
2. Search for any patron and go to "Check out" tab.
3. Add message for patron. Added message is shown in "Message" area
   with "Edit" and "Delete" links.
4. Click "Delete" and confirm modal.
5. Warning appears in plack-intranet-error.log
6. Apply the patch; restart_all
7. Repeat 3-4. No warning appears.

Sponsored-by: Ignatianum University in Cracow

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:55 -07:00
Jonathan Druart 7fdf10b9ad
Bug 41558: Fix and standardize links to tab on opac-user
We have different implemented solution to select a tab on opac-user:
* a "tab" variable (from bug 37972), it's never used
* several variables passed from the controller to the template, depending on the tab to select (it works and is used)
* use of anchors (it does not work and is used)

We should fix and standardize it.

I've linked to several reports that seem to conflict with each others.

This patch suggests to have a "tab" variable

Test plan:
Confirm that the different links to tab to opac-user are not using a
query parameter to pass the tab to select, e.g. from the opac-main page.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
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-03-05 08:33:55 -07:00
Nick Clemens 036d92e654
Bug 41316: Use patrons homelibrary for selecting overdues that should be sent
Currently the patron homelibrary option for OverdueNoticeFrom will select overdues for a branches triggers
based on the issuing branch

The other two options select the trigger/overdues based on the selection

This patch alters the selection of overdues to match only for the patrons branch

To test:
1 - Apply 41315 and follow the plan there
2 - Apply this bug and run the overdues script, assuming CPL is branch A:
    perl misc/cronjobs/overdue_notices.pl --triggered --library CPL --test --nomail -v -v
3 - No overdues should be triggered
4 - Run the script for one of the patrons home libraries, in my case MPL
    perl misc/cronjobs/overdue_notices.pl --triggered --library MPL --test --nomail -v -v
5 - Note only overdues for patrons from that branch are triggered

Signed-off-by: Ben Daeuber <bdaeuber@fargolibrary.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:54 -07:00
Owen Leonard 0b7c7eab86
Bug 40568: Various corrections to recalls templates
This patch makes various minor changes to recalls templates.

- Correct sorting of dates in tables
- Wrap recalls table in "page-section" div.
- Hide "Show old recalls" if there are no recalls.

To test, apply the patch and enable Recalls if necessary.

- If necessary, place some recalls.
- Check the sorting of date columns on the "Recalls queue" page. It
  should be correct.
- View a patron who has never placed a recall. Go to their recalls
  history and confirm that you see a "Patron has no recalls" message but
  no "Show old recalls" checkbox.

Sponsored-by: Athens County Public Libraries
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-03-05 08:33:54 -07:00
Baptiste Wojtkowski 4996ea8691
Bug 40711: Fix valuebuilder for UNIMARC 181c and 2
Test plan (UNIMARC ONLY):
1 - Make sure you have valuebuilder enabled for 181$c and 181$2 (see hereafter)
2 - Edit any record. Use valuebuilder to set 181$c to "cri - cartographic image", notice there is no change
3 - Use valuebuilder to set 181$c to "crm - cartographic moving image", notice it is set to "a"
4 - Open the valuebuilder for 181$2 and inspect the body ID. See it is cat_unimarc_field_182-2.
5 - Apply patch
6 - Repeat 2&3 and notice the codes are now cri respectively cri and crm
7 - Open the valuebuilder for 181$2 and inspect the body ID. See it is cat_unimarc_field_181-2.

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-03-05 08:33:53 -07:00
Marcel de Rooy 7f8f5cbb5c
Bug 41444: (QA follow-up) Rearrange comments
It would be at least helpful to add a reference to get_transfer here,
since we are replacing it here.
Used the opportunity to update the older comments

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:53 -07:00
Nick Clemens 13d2eb3d1b
Bug 41444: Search for transfers directly
This patch removes a fetch of the item object followed but get_transfer to simply search the
transfers by itemnumber and status

To test:
1 - Add many items to your records
    https://github.com/kidclamp/handy-koha-script/blob/main/randitems.pl
    NOTE: this script makes a lot of items withdrawn or damaged, undo that for more dramatic difference
    UPDATE items SET withdrawn=0,damaged=0
    perl misc/search_tools/rebuild_elasticsearch -v
2 - Set Koha system preferences to display more results and check more items:
    maxItemsInSearchResults 60
    numSearchResults 50
3 - Do a search, several times, note the time it takes
4 - Apply patch, restart all
5 - Repeat search several times, note the time it takes
6 - Confirm it is better after the patch

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-03-05 08:33:52 -07:00
Kyle M Hall 5779eec49b
Bug 39749: RestrictPatronsWithFailedNotices should not trigger for DUPLICATE_MESSAGE failures
Koha is built to prevent sending duplicate notices. Notices with static language ( e.g. "You have one or more holds ready to pick up" ) will easily trigger this filter.

RestrictPatronsWithFailedNotices should ignore these notices because they do not indicate any problem with contacting the patron.

Test Plan:
1) Enable RestrictPatronsWithFailedNotices
2) Enqueue to identical notices for a patron
3) Run process message queue to send one and fail the other as a
   duplicate ( alternately, just modify them in the database )
4) Run restrict_patrons_with_failed_notices.pl
5) Note the restriction
6) Remove the restriction
7) Apply this patch
8) Run restrict_patrons_with_failed_notices.pl
9) Note that no restriction was created!

Signed-off-by: Marion Durand <marion.durand@biblibre.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:52 -07:00
Lucas Gass 3d99867f60
Bug 23260: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:51 -07:00
Lucas Gass c88a86d0af
Bug 23260: DBRev 25.12.00.028
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:51 -07:00
Martin Renvoize 599286cf40
Bug 23260: (QA follow-up) Validate AnonymousPatron preference value
Add validation to ensure AnonymousPatron preference contains a valid
borrowernumber before attempting to use it in the anonymization process.
This prevents foreign key constraint errors and orphaned records if the
preference contains an invalid value (non-existent borrowernumber,
string instead of number, etc.).

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:50 -07:00
Martin Renvoize c074c66d51
Bug 23260: (QA follow-up) Optimize anonymize_last_borrowers to avoid N+1 queries
This commit refactors Koha::Patrons->anonymize_last_borrowers() to use
a single bulk UPDATE instead of looping through patrons individually.

Performance Problem:
-------------------
The original implementation had a severe N+1 query problem:
1. Query all patrons with items_last_borrower records
2. Loop through each patron (First N+1)
3. For each patron, search their items_last_borrowers (Second N+1)
4. Update each result set

For a library with 10,000 patrons and 100,000 items, this would
execute 20,000+ database queries when run as a cronjob.

The Fix:
--------
Query the items_last_borrower table directly with all conditions
in a single search, then perform one bulk UPDATE:

  $schema->resultset('ItemsLastBorrower')->search({...})->update({...})

This reduces execution from potentially thousands of queries to a
single SQL UPDATE statement, regardless of data volume.

Why This Pattern:
-----------------
While Koha::Objects->update() exists, it loops through records
individually if the object class has update()/store() methods,
unless {no_triggers => 1} is passed. Working directly with the
resultset guarantees bulk update behavior.

This pattern is used throughout Koha for bulk operations (see
Koha/Item.pm, Koha/Hold.pm, admin/koha2marclinks.pl, etc).

Testing:
--------
prove t/db_dependent/Koha/Patrons.t

All existing tests pass, confirming identical behavior with
dramatically improved performance.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:49 -07:00
Lucas Gass 2d75cac7d1
Bug 23260: Fix database update
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:49 -07:00
Lucas Gass a37d887df6
Bug 23260: Fix kohastructure.sql
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:48 -07:00
Lucas Gass fde40177fa
Bug 23260: Update cron to exit if AnonymousPatron is not set
Signed-off-by: Peter Moore <peter@mrcpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:48 -07:00
Lucas Gass 9165c0fa4b
Bug 23260: Fix DB update to NOT allow for NULL borrowernumber
Signed-off-by: Peter Moore <peter@mrcpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:47 -07:00
Andrew Fuerste Henry f0d4240ee2
Bug 23260: (QA follow-up) Prevent use of cronjob when AnonymousPatron not set
To test:
1 - configure StoreLastBorrower but not AnonymousPatron
2 - perl misc/cronjobs/anonymize_last_borrowers.pl -c
3 - the cron tells 'Preference 'AnonymousPatron' must be enabled to anonymize item's last borrower'
4 - put a value in AnonymousPatron
5 - repeat 2
6 - cron runs good

Signed-off-by: Peter Moore <peter@mrcpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:47 -07:00
Lucas Gass 7eda8d23fa
Bug 23260: Add a confirm flag
To test the confirm flag:
1. Run perl misc/cronjobs/batch_anonymise.pl --days 1
2. Notice the warning about needing a confirm flag
3. Run perl misc/cronjobs/batch_anonymise.pl --days 1 --confirm
4. The script runs

Signed-off-by: Peter Moore <peter@mrcpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:46 -07:00
Nick Clemens ecfd85ebb8
Bug 23260: Add cronjob anonymize_last_borrowers.pl
This patch adds the feature to anonymize patrons from items_last_borrower table. This code is run from batch_anonymize script, which is triggered from a new cron job anonymize_last_borrowers.pl

To test:
 1) apply this patch and the previous ones
 2) perl installer/data/mysql/updatedatabase.pl
 3) set StoreLastBorrower preference to Allow.
 4) set AnonymousPatron preference to a valid patron id
 5) Create a Check out followed by a Check in.
    CHECK => a row should appear in items_last_borrower table with the borrower and the item number.
 6) In mysql, update created_on of items_last_borrower and returndate of old_issues to two days earlier.
 7) perl misc/cronjobs/anonymize_last_borrowers.pl
    CHECK => borrower number in items_last_borrower is not anonymized yet.
 8) Repeat step 5 and 6 but instead of two days earlier, set the new entry in items_last_borrower to three days earlier.
 9) set AnonymizeLastBorrower to 'Anonymize' and AnonymizeLastBorrowerDays to 2 days.
10) perl misc/cronjobs/anonymize_last_borrowers.pl
    SUCCESS => borrower number in items_last_borrower changed to AnonymousPatron id
11) repeat step 8
12) set AnonymousPatron preference to 0
13) perl misc/cronjobs/anonymize_last_borrowers.pl
SUCCESS => borrower number in items_last_borrower changed to null
14) prove t/db_dependent/Koha/Patrons.t

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Peter Moore <peter@mrcpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:46 -07:00
Nick Clemens 39ce85bf84
Bug 23260: Unit tests
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Peter Moore <peter@mrcpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:45 -07:00
Nick Clemens 974007c0db
Bug 23260: Add anonymize_last_borrowers method to Patrons.pm
This patch adds a new routine to allow anonymizing the items_last_borrower table

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Peter Moore <peter@mrcpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:45 -07:00
Nick Clemens ab5efb97a4
Bug 23260: Database updates and new system preferences AnonymizeLastBorrower / AnonymizeLastBorrowerDays
This patch alters the items_last_borrowers table to allow for NULL borrowers

It also adds two new system preferences to allow controlling the anonymization of items_last_borrower data

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Peter Moore <peter@mrcpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:44 -07:00
Martin Renvoize e47511f889
Bug 41319: Automate sponsor tracking from git trailers
This patch replaces the hardcoded "Contributing companies and
institutions" list on the about page with an automated system that
extracts sponsor information from git commit trailers.

Changes:

1. about.pl: Load sponsor data from docs/sponsors.yaml using YAML::XS
2. about.tt: Replace hardcoded list with dynamic rendering from
   sponsors.yaml, displaying sponsors with their locations
3. docs/sponsors.yaml: Generated sponsor data file containing 212
   unique sponsors with contribution counts, locations, and URLs

The sponsor data is generated at release time using the
koha-update-sponsors script from release-tools (see release-tools
commit a30b8a3). The script:

- Extracts Sponsored-by: trailers from git history
- Applies alias resolution to consolidate duplicate names
- Preserves historical sponsors without git trailers
- Merges location and URL data from sponsor_map.yaml

This replaces the manual process of updating the sponsor list with
a maintainable approach that automatically captures all Sponsored-by:
entries from the git history while preventing duplicate entries through
alias resolution.

The about page now displays sponsors in the format:
"Organization Name, Country" instead of showing contribution counts.

To update the sponsor list at release time:
1. Run koha-update-sponsors from the Koha source directory
2. Review and commit the updated docs/sponsors.yaml

See release-tools README.md for detailed documentation.

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:44 -07:00
Owen Leonard 0ddcaa5e8a
Bug 41134: Add table settings to transfers
This patch updates the transfers page in order to add table settings to
the table of transfered items.

The patch also makes another minor correction: The transfer submit
button now has the standard class "btn-primary."

To test, apply the patch and restart services.

- Go to Circulation -> Transfer.
- Submit a barcode from a library other than your own so that the
  transfer will complete successfully.
  - Note that the "Transfer" button has the standard primary yellow
    color.
- In the table of transferred items you should see the columns setting
  menu, export option, etc. Confirm that these controls work correctly.
- Go to Administration -> Table settings -> Circulation -> Transfers
- Make some changes to the column visibility settings and return
  to the transfers page to confirm that your settings are now
  applied when you transfer more items.
  - Because of DataTables state saving feature you might have to clear
    your browser's local storage in order to see the new column
    configuration.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:43 -07:00
Lucas Gass d7aae3632e
Bug 39698: DBRev 25.12.00.027
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:43 -07:00
Jake Deery 5b59301de2
Bug 39698: Add option to expand responsive datatable
This patch adds the 'OPACTableColExpandedByDefault' system preference,
disabled by default, which controls whether responsive datatable rows
are expanded or not. This work has been performed after recommendations
from a recent accessibility audit.

TO TEST:
a)  set 'OpacLocationBranchToDisplay' to both
b)  search the OPAC for history, select first result
c)  zoom your browser in to maxiumum, scroll down to holdings table
    -   notice how the rows are collapsed by default, with '+' visible
**apply patch**
e)  refresh details page, notice how the behaviour is currently unchanged
f)  change new 'OPACTableColExpandedByDefault' syspref to 'Expand'
g)  return to OPAC details page, refresh, zoom in
    -  notice how the rows are now expanded by default, with '-' visible
**sign off**

Sponsored-by: British Museum
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:42 -07:00
Shi Yao Wang 73c320e751
Bug 39580: Make Elasticsearch process_error error string translatable
Test plan:
1- Change to a language other than english
2- Search in the catalog with "!"
3- Notice the string "Unable to understand your search query, please
 rephrase and try again." is not translated
4- Apply the patch
5- Add translation in .po file of used language for "Unable to
understand your search query, please rephrase and try again.\n"
6- Update translation
7- Redo the search and notice the string is now translated

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:42 -07:00
Kyle M Hall 3487b56e20
Bug 36542: Update unit test
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:41 -07:00
Arthur Suzuki c3ce3b17c7
Bug 36542: after_biblio_action plugin hook is now called after the dbx transaction is finished
To reproduce :
1) Install a plugin which uses the biblionumber of a created record in the "after_biblio_action".
2) Verify the biblionumber is missing

This is because the call to the hooks is done before the DB transaction is actually done (in AddBiblio function).

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:41 -07:00
Pedro Amorim 0af6ad8cd5
Bug 40105: (QA follow-up): Have a 'additional information' section
This will encompass the 'Notes' field plus custom fields.

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:40 -07:00
phette23 5940fb39d5
Bug 40105: Patrons cannot add notes when creating an ILL
Testing plan:
- Enable the ILLModule system preference
- Sign into the OPAC and make an ILL request at /cgi-bin/koha/opac-illrequests.pl
- Note there's no option to specify notes
- Apply the patch and restart services
- Make another ILL request
- Use the new notes field
- View the request and note that the notes value was saved

Sponsored-By: California College of the Arts
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-03-05 08:33:32 -07:00
Jake Deery c3cf41c275
Bug 41619: (Follow-up) Fix for t/Koha/CSV.t
This patch fixes an omission that caused t/Koha/CSV.t to fail. To test, prove t/Koha/CSV.t and notice how it now passes.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 13:12:37 -07:00
Martin Renvoize 300f121b66
Bug 35612: (QA follow-up) Add test for undefined LostChargesControl preference
Adds test coverage for the case where LostChargesControl system
preference is not set (returns undef). This ensures the code
handles this gracefully and defaults to ItemHomeLibrary behavior.

The test verifies that:
- No warnings are generated when the preference is undefined
- The default behavior (ItemHomeLibrary) is applied correctly
- The correct branch (item homebranch) is recorded in accountlines

This improves test coverage and prevents regression of the fix
for uninitialized value warnings.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:54 -07:00
Martin Renvoize 30510e1d0a
Bug 35612: (QA follow-up) Document undef handling in branch_for_fee_context
Improves POD documentation for branch_for_fee_context to:
- Document all parameters (patron, item, issue objects)
- Explain the resolution logic for each control preference
- Clarify that returning undef is acceptable behavior
- Note how undef is handled by calling code

Adds inline comment explaining that undef return value is
gracefully handled by Koha::Account->add_debit.

This addresses concerns about unclear undef handling and
improves maintainability.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:53 -07:00
Martin Renvoize 73ae8a15db
Bug 35612: (QA follow-up) Use interface parameter and remove self-referential use
This patch fixes two issues:

1. C4::Accounts::chargelostitem now properly uses the $interface
   variable that was set from $opts->{interface} instead of calling
   C4::Context->interface directly. This ensures the interface
   parameter is honored, particularly important for cronjob context.

2. Removes the self-referential 'use Koha::Checkout;' statement in
   Koha/Checkout.pm which is logically incorrect (a module should
   not use itself).

Test plan:
1. Run prove t/db_dependent/Accounts.t
2. Run prove t/db_dependent/Koha/Checkout.t
3. Verify all tests pass

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:53 -07:00
Martin Renvoize b6695f75bd
Bug 35612: (QA follow-up) Fix uninitialized value warnings
When LostChargesControl system preference is not set (undefined),
the code was generating 'Use of uninitialized value' warnings
when comparing $lost_control_pref in string equality checks.

This adds defined checks before the comparisons to handle the
case where the preference is undefined, treating it the same as
'ItemHomeLibrary' (the default).

Test plan:
1. Run prove t/db_dependent/Circulation.t
2. Verify all tests pass without warnings

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:52 -07:00
Laura_Escamilla 3308b7f61c
Bug 35612: Add tests for chargelostitem branch context
Adds test coverage for C4::Accounts::chargelostitem to verify that
LOST and PROCESSING fee debits correctly record branchcode based on
LostChargesControl and HomeOrHoldingBranch system preferences.

Tests verify all scenarios:

1. LostChargesControl behavior:
   - PatronLibrary: Uses patron's home branch
   - ItemHomeLibrary + homebranch: Uses item's home library
   - ItemHomeLibrary + holdingbranch: Uses item's holding library
2. Explicit library_id override:
   - When library_id is passed in options, it takes precedence
3. Deduplication logic:
   - Prevents duplicate LOST charges for same itemnumber+issue_id
   - Regression test for item_id column name bug
4. Issue_id handling:
   - Correctly looks up issue_id when not provided
   - Handles bundled items (looks up host item's checkout)
5. Processing fees:
   - Honors the same branch logic as LOST fees
   - Correctly applies lost_item_processing_fee circulation rule
     using the appropriate branch context

This test subtest was added to the existing "C4::Accounts::chargelostitem tests"
section and increases the test plan from 4 to 5 subtests.

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:52 -07:00
Laura_Escamilla e45e06dcf5
Bug 35612: Fix incorrect primary key reference
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:51 -07:00
Laura_Escamilla 0832933a96
Bug 35612: Add tests for branch context recording in LOST fees via LostItem
Adds comprehensive test coverage for C4::Circulation::LostItem to verify
that LOST and PROCESSING fee debits correctly record branchcode based on
LostChargesControl and HomeOrHoldingBranch system preferences.

Tests verify:
- LostChargesControl = PatronLibrary uses patron's home branch
- LostChargesControl = PickupLibrary uses checkout/issue branch
- LostChargesControl = ItemHomeLibrary respects HomeOrHoldingBranch:
  * homebranch: uses item's home library
  * holdingbranch: uses item's holding library

Tests cover both LOST and PROCESSING fee types and verify the branch
context is preserved when fees are charged via the LostItem function,
which is called both from the staff interface when manually marking
items lost and from the longoverdue cronjob.

The tests also verify correct behavior when mark_lost_from = 'cronjob'
to ensure the interface context is properly set for batch operations.

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:51 -07:00
Laura_Escamilla c7764f1140
Bug 35612: Add tests for branch context recording in OVERDUE fees
Adds comprehensive test coverage for C4::Overdues::UpdateFine to verify
that the branchcode field is correctly populated based on CircControl
and HomeOrHoldingBranch system preferences.

Tests verify:
- CircControl = PatronLibrary uses patron's home branch
- CircControl = PickupLibrary uses checkout/issue branch
- CircControl = ItemHomeLibrary respects HomeOrHoldingBranch:
  * homebranch: uses item's home library
  * holdingbranch: uses item's holding library

The tests create distinct libraries for each context (patron home,
item home, item holding, checkout branch) and verify the correct
branch is recorded in accountlines.branchcode for OVERDUE debits.

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:50 -07:00
Laura_Escamilla 60728c63bb
Bug 35612: Add unit test for Koha::Checkout->branch_for_fee_context
Adds test coverage for the new branch_for_fee_context class method.
Tests are minimal at this stage but verify the method exists and
can be called. More comprehensive testing is covered in the
C4::Accounts, C4::Circulation, and C4::Overdues test additions.

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:50 -07:00
Laura_Escamilla 82ff1e700b
Bug 35612: Recorded branch context for OVERDUE, LOST, and PROCESSING fees
This patch ensures that the accountlines.branchcode field correctly records
which branch's circulation rules were applied when creating OVERDUE, LOST,
and PROCESSING fee debits. Previously, this field was often NULL or set
to the logged-in user's branch, making it difficult to track which branch's
policies governed the fee calculation.

Changes made:

1. New helper method: Koha::Checkout->branch_for_fee_context()
   - Centralizes branch resolution logic based on system preferences
   - For LOST/PROCESSING: honors LostChargesControl preference
   - For OVERDUE: honors CircControl preference
   - Respects HomeOrHoldingBranch when choosing item's branch
   - Supports both Koha objects and plain hashrefs
   - Returns branchcode or undef if context unavailable
2. C4::Accounts::chargelostitem()
   - Now accepts optional $opts hashref parameter
   - Accepts interface, library_id, and issue_id in options
   - Determines appropriate branch using branch_for_fee_context
   - Passes resolved branch to add_debit as library_id
   - Issue_id lookup handles bundled items correctly
3. C4::Circulation::LostItem()
   - Calls branch_for_fee_context before chargelostitem
   - Passes interface context ('cron' for cronjob, otherwise current)
   - Passes resolved branch and issue_id to chargelostitem
4. C4::Overdues::UpdateFine()
   - Calls branch_for_fee_context to determine OVERDUE branch
   - Passes result as library_id to add_debit
   - Resolves long-standing FIXME about branch selection
5. misc/cronjobs/longoverdue.pl
   - Determines branch context before marking items lost
   - Passes library_id to LostItem when charging fees

The branch resolution logic respects these system preferences:
- CircControl: PatronLibrary | PickupLibrary | ItemHomeLibrary
- LostChargesControl: PatronLibrary | PickupLibrary | ItemHomeLibrary
- HomeOrHoldingBranch: homebranch | holdingbranch

Test plan:
1. Create three test libraries (e.g., FPL, CPL, PVL)
2. Create a test patron with home library PVL
3. Create an item with:
   - homebranch = FPL
   - holdingbranch = CPL
4. Set system preferences:
   - FinesMode: Calculate and charge
   - Create circulation rule that charges overdue fines
5. Test OVERDUE fees with CircControl:
   a. Set CircControl = PatronLibrary, HomeOrHoldingBranch = homebranch
   b. Check out item to patron at CPL, backdate to be overdue
   c. Run: perl /usr/share/koha/bin/cronjobs/fines.pl
   d. Check accountlines: SELECT accountlines_id, branchcode, debit_type_code
      FROM accountlines ORDER BY accountlines_id DESC LIMIT 5;
   e. Verify branchcode = PVL (patron's home library)
   f. Delete the fine, repeat with CircControl = PickupLibrary
   g. Verify branchcode = CPL (checkout library)
   h. Delete the fine, repeat with CircControl = ItemHomeLibrary
   i. Verify branchcode = FPL (item's home library)
   j. Change HomeOrHoldingBranch = holdingbranch, repeat
   k. Verify branchcode = CPL (item's holding library)

6. Test LOST fees with LostChargesControl:
   a. Set WhenLostChargeReplacementFee = Charge
   b. Set item replacement price > 0
   c. Ensure item is checked out to patron
   d. Set LostChargesControl = PatronLibrary
   e. Mark item as lost in staff interface
   f. Check accountlines for LOST debit
   g. Verify branchcode = PVL (patron's home library)
   h. Return item, re-checkout, repeat with LostChargesControl = PickupLibrary
   i. Verify branchcode = CPL (checkout library)
   j. Repeat with LostChargesControl = ItemHomeLibrary + homebranch
   k. Verify branchcode = FPL (item's home)
   l. Repeat with LostChargesControl = ItemHomeLibrary + holdingbranch
   m. Verify branchcode = CPL (item's holding)
7. Test longoverdue cronjob:
   a. Re-checkout item with due date triggering longoverdue
   b. Run: perl /usr/share/koha/bin/cronjobs/longoverdue.pl --lost 30=1 --charge 1 --confirm --verbose
   c. Verify LOST debit created with correct branchcode per LostChargesControl
8. Test edge cases:
   - WhenLostChargeReplacementFee = Don't charge → no LOST debit
   - Replacement price = 0 → no LOST debit
   - FinesMode = Don't calculate → no OVERDUE debits

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:49 -07:00
Lucas Gass 0a58a77d1d
Bug 36506: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:49 -07:00
Lucas Gass 9fa84c7fd4
Bug 36506: DBRev 25.12.00.026
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:48 -07:00
Martin Renvoize 08409dd450
Bug 36506: (QA follow-up) Add test for missing circulation rule
This adds a test case to verify that when no lost_item_processing_fee
circulation rule exists for a branch/itemtype combination, the system
correctly defaults to 0 (no processing fee charged).

The test confirms:
- No PROCESSING account line is created when the rule is missing
- The LOST fee is still created normally (only processing fee affected)
- The // 0 fallback in C4::Accounts::chargelostitem works correctly

Test plan:
1. prove t/db_dependent/Accounts.t
2. Verify all tests pass, including the new "missing circulation rule"
   test case

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:48 -07:00
Nick Clemens 88542c7a64
Bug 36506: Unit tests
Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:47 -07:00
Nick Clemens cd0462b4c6
Bug 36506: Adjust existing tests and test builder
All the previously written tests should continue to pass.

Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:47 -07:00
Nick Clemens 2b5f89788d
Bug 36506: Use new rules in C4::Accounts
This seems to be the only place processing fee is used, in chargelostitem

This patch uses get_effective_rule_value, falling back to 0 if no rule.

We also respect the new system preference, LostChargesControl, introduced in bug 39802

To test:
 1 - Apply patch, updatedatabase, restart all
 2 - Set a default/all branches  processing rule for Books itemtype to 5
 3 - Set a rule for branch 1 to 1
 4 - Set a rule for branch 2 to 2
 5 - Set a rule for branch 3 to 3
 6 - Set a rule for branch 4 to 4
 7 - Set LostChargesControl to 'library the patron is from'
 8 - Check out a Books item from a different branch to a patron from branch 1
 9 - Mark item lost, confirm processing fee is charged as 1
10 - Check out out a Books item from a different branch to a patron from branch 5
11 - Mark item lost, confirm processing fee is 5
12 - Set LostChargesControl to 'library you are logged in at'
13 - Check out a Books item from a different branch to a patron from branch 1
14 - Ensure you are logged in at branch 2
15 - Mark item lost, confirm processing fee is 2
16 - Set LostChargesControl to 'the library the item is from'
17 - Set HomeOrHoldingBranch to 'homebranch'
18 - Still at branch 2, check out a books item from branch 3 to a patron of branch 1, this ensure holdingbranch is not 3
19 - Mark item lost, confirm processing fee is 3
20 - Set HomeOrHoldingBranch to 'holdingbranch'
21 - Ensure you are logged in at branch 4
22 - Checkout an item from branch 1 to a patron from a different branch
23 - Mark item lost, confirm processing fee is 4
24 - Test other combos as you see fit
25 - Sign off!

Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:46 -07:00
Nick Clemens f4634ee1c8
Bug 36506: Remove process fee from itemtype administration
Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:45 -07:00
Nick Clemens 02702346dc
Bug 36506: Remove API mapping
Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:45 -07:00
Nick Clemens 5ea97b7dd5
Bug 36506: Database update
Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:44 -07:00
Nick Clemens 8d6391d3f0
Bug 36506: Add lost_item_processing_fee rule and CRUD
Sponsored-by: The Main Library Alliance <https://www.mainlib.org/>
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:44 -07:00
Aleisha Amohia c72fc81b76
Bug 38549: Provide surname for create_superlibrarian.pl
Interestingly this script already had a $surname variable, just never accepted a parameter for it.

This enhancement enables the user to supply a surname parameter when creating a superlibrarian user via the commandline create_superlibrarian.pl script. Koha patrons MUST have a name, so if no surname is provided, the userid will be used for the surname instead.

To test:

1. Apply the patch and restart services
2. Attempt to create a superlibrarian with no surname provided.
  In KTD: misc/devel/create_superlibrarian.pl --userid test --cardnumber test --password XXXX --branchcode CPL --categorycode S
3. Search for your patron in Koha. Confirm the surname was automatically set to the userid 'test'
4. Attempt to create a superlibrarian, this time provide a surname that is different from the userid so easy to tell apart.
  In KTD: misc/devel/create_superlibrarian.pl --userid test1 --cardnumber test1 --password XXXX --branchcode CPL --categorycode S --surname testsurname
5. Search for your patron in Koha. Confirm the surname was set correctly using the given surname.

Sponsored-by: Catalyst IT
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:43 -07:00
Owen Leonard 53dce07860
Bug 41170: (follow-up) Correct a CSS selector and adjust colors
This patch updates the table cell selector so that the
"previous" class isn't overwritte, and updates the "active" and
"previous" classes so that they use standard Bootstrap colors.

Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:43 -07:00
Nick Clemens a6819d4985
Bug 41170: Highlight previously edited item on add items page
Some libraries find it useful to have the item that was just
edited/saved visible in the items list

To test:
0 - Apply patch
1 - Edit an item, confirm the itme is highlighted yellow with title
    'Currently editing' (Hover to see the title)
2 - Save the item
3 - Confirm item is now highlighted light blue with title 'Previously
    edited'
4 - Sign off

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:42 -07:00
Owen Leonard 899e4001e8
Bug 24949: Provide password visibility toggle / icon to unmask password on staff login screen
This patch adapts the work on Bug 34778 to the staff client, adding a
"Show password" checkbox to the login form.

To test, apply the batch and go to the staff client login screen.

- You should see a "Show password" checkbox under the password field.
- Type something in the password field. It should be masked (****).
- Click the "Show password" label or check the checkbox.
- The password field should now show the text you entered.
- Confirm that you can log in successfully whether or not the password
  is visible.
- `prove t/db_dependent/selenium/authentication.t` should pass.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Mikko <mikko.liimatainen@turku.fi>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:42 -07:00
Owen Leonard 8d2b17ae78
Bug 24949: Stylelint corrections for login.css
This patch includes automated stylelint corrections for login.css. It
should have no visible effect.

To test, apply the patch and clear your browser cache. View the staff
interface login form and confirm that everything looks correct.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:41 -07:00
Owen Leonard fa168c3840
Bug 25314: (follow-up) Fix style of custom search filter clear button
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:41 -07:00
Owen Leonard 2546c08688
Bug 25314: Make OPAC facets collapse
This patch implements collapsing facets using the Bootstrap Collapse
component.

To test, apply the patch and rebuild the OPAC CSS.

1. Clear your browser cache if necessary.
2. Using the sample data, go to Advanced Search in the OPAC. Check the
   "Books" checkbox under the "Item types" tab and submit.
3. In the left-hand sidebar you should see the facets you normally see,
   but now each one has an arrow icon to show whether the facet is
   expanded or collapsed.
4. All facets should be expanded by default. Adding expanded/collapsed
   settings to system preferences is out of scope for this bug.
5. Test expanding and collapsing facets, as well as the "Show more" link
   under each one.
6. Click the "Fiction" link under the "Collections" facet. When the
   refined search results load you clicked should now have a red "X"
   link next to "Fiction," which should no longer be a link.
   - Because "Fiction" is the only uption under the "Collections" facet,
     the facet is not collapsible. I reasoned that it would be important
     not to let a facet limitation disappear behind hidden facets.
   * The only way I know to do this in the template is to check for
     facets with only one option this has the possibly-unwanted side
     effect of making facets which haven't been selected but still only
     have one option not collapsible. In this example search "Holding
     libraries" and "Topics" show this. I'm open to suggestions on this!
7. Test with the displayFacetCount preference both enabled and disabled.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:40 -07:00
Wainui Witika-Park f2dbadd52a
Bug 36853: Enhance Elasticsearch Notes/Comments MARC21 index mappings
Added the following tags and subfields to the existing note index for Elasticsearch MARC21, as used by the "Notes/Comments" advanced search dropdown option: 501a, 503a, 504a, 508a, 511a, 521a, 538a, 547a

Sponsored-by: Educational Services Australia SCIS
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Thomas Klausner <domm@plix.at>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:40 -07:00
David Cook 6da9009b3a
Bug 38946: Add enumchron to inventory table
This patch adds "Serial enumeration / chronology" to the inventory table
output.

Test plan:
0. Apply the patch
1. Go to http://localhost:8081/cgi-bin/koha/tools/inventory.pl
2. Click "Submit"
3. Note that a "Serial enumeration / chronology" column appears in
the results
4. For the top result, go into the bib record and then add a "Serial enumeration /
chronology" to the item in question
5. Re-run the inventory
6. Note that the "Serial enumeration / chronology" now has a value in iy
7. Click "Columns" and hide the column
8. Try again using the CSV export and note that the new column appears again

Signed-off-by: Ludovic Julien <ludovic.julien@inLibro.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-27 11:38:33 -07:00
Tomás Cohen Arazi b16a541537
Bug 41616: Silence warning in authority_hooks.t
This patch silences a useless warning that is printed when in ES context.

To test:
1. Launch a KTD instance with ES enabled:
   $ ktd --proxy --name 41616 --search-engine es8 up -d
   $ ktd --name 41616 --wait-ready 120
2. Run:
   $ ktd --name 41616 --shell
  k$ prove t/db_dependent/Koha/Plugins/authority_hooks.t
=> SUCCESS: Tests pass
=> FAIL: You see:

```shell
prove t/db_dependent/Koha/Plugins/authority_hooks.t
t/db_dependent/Koha/Plugins/authority_hooks.t ................. 1/5 Plugin error (Test Plugin): Exception 'Koha::Exception' thrown 'elasticsearch_to_document ref record: MARC::Record - ref document: HASH'
Plugin error (Test Plugin): Exception 'Koha::Exception' thrown 'elasticsearch_to_document ref record: MARC::Record - ref document: HASH'
t/db_dependent/Koha/Plugins/authority_hooks.t ................. ok
All tests successful.
Files=20, Tests=119, 24 wallclock secs ( 0.04 usr  0.02 sys + 17.89 cusr  2.33 csys = 20.28 CPU)
Result: PASS
```

3. Apply this patch
4. Repeat 2
=> SUCCESS: No tests changes, but no more weird warnings about elasticsearch
5. Sign off :-D
6. Cleanup:
   $ ktd --name 41616 down

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:24 -07:00
grgurmg c8800512ab
Bug 41058: Assign timeout id when delaying issues table load
When AlwaysLoadCheckoutsTable is enabled and a delay is configured,
we were calling setTimeout without capturing the returned id, despite a
`loadIssuesTableDelayTimeoutId` variable already being declared and targetted
by clearTimeout. This prevented the "Load now" button from properly clearing
the pending timeout.

Test plan:
1. Configure a delay (LoadCheckoutsTableDelay > 0) so checkouts table
   loads after N seconds.
2. Go to a patron’s checkouts page.
3. Before the delay completes, click "Load now".
   - Without this patch: an alert window will display a "Something went wrong
	 when loading the table" error once the timeout triggers.
   - With this patch: the pending timeout is cleared; the table loads
     immediately only once.
4. Reload the page with the delay set; wait for the timeout to elapse
   and confirm the table loads normally.

Signed-off-by: Mike Grgurev <mike.grgurev@mainlib.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:23 -07:00
David Cook ea16d2288d
Bug 41647: Fix self-checkin to use responsive CSS
Test plan:
0. Apply the patch
1. Enable the SelfCheckInModule system preference
2. Go to http://localhost:8080/cgi-bin/koha/sci/sci-main.pl
3. Enter barcode 39999000004571 into input box, click "Submit",
then click "Check in"
4. Using your F12 dev tools, look at the interface with different
widths
5. Note that the page looks good on both large and small screens
6. Note that the page stays contained to the small screen size
and you don't have to scroll horizontally to see all the content

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:23 -07:00
David Cook f73b738db3
Bug 41518: Only show autorenewal text if autorenewal allowed
This change makes the "Scheduled for automatic renewal" text appear
in the staff interface and OPAC only if both the issue has auto_renew
and the borrower has autorenew_checkouts set. These are the criteria that
automatic_renewals.pl uses for processing automatic renewals.

Test plan:
0. Apply the patch
1. koha-plack --restart kohadev
2. Checkout an item as an automatic renewal
(ie either set it as Automatic renewal using the checkout option or have
 the circ and fines rules set to use Automatic renewals)
3. View the checkout in both the staff interface and the OPAC
4. Note that they both show "Scheduled for automatic renewal" or
"This item is scheduled for auto renewal" for the OPAC
5. In the patron record change "Allow auto-renewal of items" from
"Yes" to "No"
6. Note that the text talking about the item being scheduled
for automatic renewal no longer displays

Bonus points:
1. Run 'perl ./misc/cronjobs/automatic_renewals.pl -v'
2. Note that the automatic renewal doesn't show when "Allow auto-renewal
of items" is set to "No" but it does show when it's set to "Yes"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Emily Lamancusa <emily.lamancusa@montgomerycountymd.gov>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:22 -07:00
Paul Derscheid ee667d3ddf
Bug 41887: Skip clash detection on terminal status transition
- Booking::store runs item-level and biblio-level clash
  detection unconditionally on every call
- During checkout, C4::Circulation sets the booking status to
  'completed' and calls ->store(), which triggers clash checks
- Combined with false positives from Biblio::check_booking
  (e.g. checkouts on non-bookable sibling items inflating the
  unavailable count), this throws
  Koha::Exceptions::Booking::Clash resulting in a 500 error
- Running clash detection on a terminal transition is
  unnecessary regardless; skip it for cancelled/completed
- Add _is_final_status_transition() helper that inspects the
  dirty columns to detect completed/cancelled transitions
- Guard both clash detection blocks with the new helper

To test:
1. Have a biblio with two items: one bookable, one not
   (bookable = 0).
2. Create a booking on the bookable item.
3. Check out the non-bookable item to a patron.
4. Check out the bookable item to the booking patron (this
   triggers the completed transition via C4::Circulation).
5. Without the patch: 500 error from
   Koha::Exceptions::Booking::Clash.
6. Apply the patch.
7. Repeat steps 2-4.
8. Verify the checkout succeeds and the booking transitions
   to completed.
9. Run: prove t/db_dependent/Koha/Booking.t
   and confirm the new subtest "store() skips clash
   detection on terminal status transition" passes.

Sponsored-by: Büchereizentrale Schleswig-Holstein <https://www.bz-sh.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-02-26 09:04:22 -07:00
Paul Derscheid c39021d1bc
Bug 40896: (follow-up) Fix bfcache double-toggle on form button elements
- Track pending reset timeouts in a WeakMap keyed by element
- Clear any pending timeout in the pageshow handler before re-enabling
- Prevents the resumed setTimeout from re-disabling the button after
  bfcache restoration

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:21 -07:00
Paul Derscheid 53cc39ae2d
Bug 40896: (follow-up) Use icon wrapper elements and visibility toggling
- Wrap icons in semantic container elements for visibility toggling
- Create throttled-button.inc with Template Toolkit BLOCKs to reduce duplication
- Extract throttle logic to throttledButton.js with event delegation
- Make timeout configurable via data-throttle-timeout attribute
- Replace string-based modal generation with template element to support
  SSR

To test:
1) Follow the test plan from the previous commit
2) Additionally verify the throttled button behavior works in all locations:
   - Reports list table: "Run" button for each saved report
   - Report toolbar: "Run report" button (when viewing/editing a report)
   - Parameters form: "Run the report" button
   - Edit SQL form: "Update and run SQL" button
   - Preview SQL modal: "Run report" button (click "Preview SQL" in dropdown)
3) For each location, confirm:
   - Button shows play icon initially
   - Upon click, button becomes disabled with spinner icon
   - Button has aria-busy="true" when disabled
   - After navigation/submission, button state is handled correctly
4) Test the modal button specifically:
   - Open Preview SQL modal from any report dropdown
   - Click "Run report" in the modal
   - Verify throttle behavior works for this dynamically created button
5) Verify that the set timeout works well, I thought 120 seconds is too
   long if you go back via bfcache (back button in the browser). You'd
   have to either reload or wait 2 minutes for the button to become
   interactive again.
6) Sign off

Signed-off-by: Jake Deery <jake.deery@openfifth.co.uk>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:21 -07:00
Jake Deery 9067b2aafe
Bug 40896: Disable run report buttons upon click
This patch will disable the run report button found on both the list page, and the individual operation pages (e.g. run, edit, saved successfully). This will stop librarians from unintentionally crashing their Koha system, by pressing the run report button repeatedly when there is no need to re-request it.

TO TEST:
a)  go to /cgi-bin/koha/reports/guided_reports.pl?op=list
b)  create three new sql test reports:
    *)  Name: Test report #1, SQL: SELECT count(*) FROM biblio
    *)  Name: Test report #2, SQL: SELECT count(*) FROM items
    *)  Name: Test report #3, SQL: SELECT * FROM items WHERE items.dateaccessioned =  <<Date accessioned|date>>
c)  run all of the above test reports, noticing how the run report button can be activated repeatedly
APPLY PATCH
d)  redo step a
e)  run test report #1 from the table, notice how now the button can only be activated once before it is greyed out and the play button is replaced with a spinner
f)  return to the list, edit test report #2 from the table, click run report from the edit page, and notice how this run report button exhibits the same behaviour as the button in step e
g)  return to the list, run test report #3, select a date, click run report from this page, and notice again how the run report button exhibits the same behaviour as the buttons in steps e and f
SIGN OFF

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:20 -07:00
Paul Derscheid 560520305c
Bug 41886: Filter checkouts to bookable items in check_booking
- Biblio::check_booking counts current checkouts toward the
  unavailable pool to detect booking clashes
- It counts checkouts on ALL items for the biblio, including
  non-bookable ones (e.g. items with an excluded item type)
- When only a subset of items are bookable, checkouts on
  non-bookable siblings incorrectly reduce the available pool,
  producing false clashes
- Restrict the checkout query to only count items whose
  itemnumber is in the bookable items set

To test:
1. Have a biblio with at least two items, where one item is
   bookable and the other is not (bookable = 0).
2. Check out the non-bookable item to a patron.
3. Attempt to create a booking for the bookable item.
4. Without the patch: a clash is detected even though the
   bookable item is available.
5. Apply the patch.
6. Repeat steps 2-3.
7. Verify the booking succeeds without a false clash.
8. Run: prove t/db_dependent/Koha/Biblio.t
   and confirm the new subtest
   "checkouts on non-bookable items do not cause false clashes"
   passes.

Sponsored-by: Büchereizentrale Schleswig-Holstein <https://www.bz-sh.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-02-26 09:04:20 -07:00
Lucas Gass 36997e5493
Bug 41131: DBRev 25.12.00.025
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:19 -07:00
Nick Clemens 4c82ff273b
Bug 41131: Don't allow limiting transfers from/to a single branch
We say that you cannot limit transfers internally within a branch, but
it seems we do allow you to, and don't show you when we do

To test:
1 - Go to Admin->Library transfer limits
2 - On a branches page, uncheck the box for that branch
    e.g. don't allow FIC to transfer to CPL from CPL
3 - Save
4 - Note the box is still checked
5 - Check the DB
    SELECT * FROM branch_transfer_limits;
6 - There is a line to/from CPL!
7 - Apply patches
8 - updatedatabase
9 - Repeat 5 - the line is deleted!
10 - Try to create the rule again, not the box is now disabled
11 - Be sneaky, enable the box, uncheck it and save, no rule created
12 - Be sneaky, edit the HMTL for another button to make it limiting CPL to CPL, save, no rule created
13 - Sign off

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:19 -07:00
David Cook 017b157b7d
Bug 41599: Fix output_error in acquisitions_stats.pl
This patch changes the incorrect usage of output_error
in acquisitions_stats.pl so that it uses a HTTP status
code instead of an arbitrary string.

Test plan:
0. Apply the patch and koha-plack --restart kohadev
1. Go to http://localhost:8081/cgi-bin/koha/reports/acquisitions_stats.pl
2. Use your F12 HTML inspector to find the "Line" radio buttons that you've
selected and remove the values for those elements
3. Click "Submit" at the bottom of the screen
4. Note that you get a 403 error (if bug 30803 applied - otherwise it's a 404 error)

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:18 -07:00
Jonathan Druart 6b61f010f4
Bug 30803: Raise exception if $error is not passed
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:18 -07:00
Jonathan Druart 06c557cfad
Bug 30803: Add tests
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:17 -07:00
David Cook beea9253e5
Bug 30803: Don't hardcode 404 into output_error
This patch removes the hardcoded "404 Not Found" status
code and reason phrase from output_error and instead
uses just the code passed into the function.

Test Plan:
0. Apply patch and koha-plack --restart kohadev
1. Go to http://localhost:8080/cgi-bin/koha/tracklinks.pl?uri=https%3A%2F%2Ftest.com&biblionumber=29
2. Note normal OPAC 404 error

Bonus points:
1. Change 404 to 403 in the first output_error in opac/tracklinks.pl
2. koha-plack --restart kohadev
3. Go to http://localhost:8080/cgi-bin/koha/tracklinks.pl?uri=https%3A%2F%2Ftest.com&biblionumber=29
4. Note normal OPAC 403 error

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:17 -07:00
Janusz Kaczmarek 184e966de4
Bug 41864: (Bug 40966 follow-up) Simple OPAC search generates warnings: Odd number of elements in anonymous hash
After introducing Bug 40966 opac-search.pl, when invoked in the
simple form (not as advanced search) generates warnings like:

[WARN] Odd number of elements in anonymous hash at /kohadevbox/koha/opac/opac-search.pl line 529.

Test plan:
==========
1. Perform a simple search from main OPAC page.
2. Check the content of plack-opac-error.log. You should see a
   warning like:
   [WARN] Odd number of elements in anonymous hash at /kohadevbox/koha/opac/opac-search.pl line 529.
3. Apply the patch ; restart_all.
4. Repeat p. 1. There should be no new warning now.

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:16 -07:00
Lucas Gass 1c50f6b774
Bug 41761: Do not update accountlines.date when editing a note
To test:
0. APPLY PATCH, restart_all
1. Create an accountline and make note of the accountline.date
2. On that accoutnline edit the note.
3. Confirm that the accountline.date does NOT update.

Signed-off-by: Juliet Heltibridle <jheltibridle@rcplib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:16 -07:00
Andreas Roussos 7ee861e256
Bug 41337: Add missing Bash variable to `koha-create`
When creating a new Koha instance using the two-step workflow
(`koha-create --request-db <instance>`, followed by
`koha-create --populate-db <instance>`), some log files under
/var/log/koha/<instance>/ end up with incorrect ownership.

Specifically, intranet-error.log and opac-error.log are owned
by root:root instead of <instance>-koha:<instance>-koha.

This patch fixes that.

Test plan (tailored for KTD):

1) Before applying this patch, run the following commands
   inside your KTD Koha container:

   $ sudo koha-create --request-db test1
   $ SQLPASS=`sudo grep password: test1-db-request.txt | cut -c 16-`
   $ mysql -hdb -uroot -ppassword -e "CREATE DATABASE koha_test1;"
   $ mysql -hdb -uroot -ppassword -e "GRANT ALL PRIVILEGES ON koha_test1.* TO 'koha_test1'@'%' IDENTIFIED BY '${SQLPASS}';"
   $ mysql -hdb -uroot -ppassword -e "FLUSH PRIVILEGES;"
   $ sudo koha-create --populate-db test1

   $ ls -l /var/log/koha/test1/
     total 8
     -rw-r--r-- 1 test1-koha test1-koha    0 Feb  9 15:35 indexer-error.log
     -rw-r--r-- 1 test1-koha test1-koha 1278 Feb  9 15:35 indexer-output.log
     -rw-r--r-- 1 root       root          0 Feb  9 15:35 intranet-error.log
     -rw-r--r-- 1 root       root          0 Feb  9 15:35 opac-error.log
     -rw-r--r-- 1 test1-koha test1-koha    0 Feb  9 15:35 worker-error.log
     -rw-r--r-- 1 test1-koha test1-koha 1828 Feb  9 15:35 worker-output.log
     -rw-r--r-- 1 test1-koha test1-koha    0 Feb  9 15:35 zebra-error.log
     -rw-r--r-- 1 test1-koha test1-koha    0 Feb  9 15:35 zebra-output.log

   Notice the incorrect user/group ownership on two
   files: `intranet-error.log` and `opac-error.log`.

2) Apply this patch, remove the `test1` instance along
   with the database request text file, and copy the
   modified script to its system-wide location:

   $ sudo koha-remove test1
   $ sudo rm test1-db-request.txt
   $ sudo cp debian/scripts/koha-create /usr/sbin/koha-create

3) Repeat the commands in Step 1. This time, the output of
   the last command should resemble the following:

   total 8
   -rw-r--r-- 1 test1-koha test1-koha    0 Feb  9 15:42 indexer-error.log
   -rw-r--r-- 1 test1-koha test1-koha 1520 Feb  9 15:42 indexer-output.log
   -rw-r--r-- 1 test1-koha test1-koha    0 Feb  9 15:42 intranet-error.log
   -rw-r--r-- 1 test1-koha test1-koha    0 Feb  9 15:42 opac-error.log
   -rw-r--r-- 1 test1-koha test1-koha    0 Feb  9 15:42 worker-error.log
   -rw-r--r-- 1 test1-koha test1-koha 1828 Feb  9 15:42 worker-output.log
   -rw-r--r-- 1 test1-koha test1-koha    0 Feb  9 15:42 zebra-error.log
   -rw-r--r-- 1 test1-koha test1-koha    0 Feb  9 15:42 zebra-output.log

   All files should now have the correct user/group ownership.

Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:15 -07:00
John Doe f9bab53f22
Bug 41036: (QA follow-up) use nowarnings
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:15 -07:00
Lari Taskula f54cce344c
Bug 41036: Log errors in Koha::ImportBatch->new_from_file
To test:
1. prove t/db_dependent/Koha/ImportBatch.t
2. Observe success

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:14 -07:00
Lari Taskula bcf4531288
Bug 41036: Add tests
To test:
1. Apply only this patch, do not yet apply the next one
2. prove t/db_dependent/Koha/ImportBatch.t
3. Observe tests failing

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:14 -07:00
Slava Shishkin cea8d5aa6b
Bug 41675: Fix Quick add patron username being overwritten
Test plan:
1. In system preferences, set PatronQuickAddFields to include userid
   (Username).
2. Go to Patrons and choose Quick add new patron.
3. Fill at least Surname, Card number, and Username
   (use a value clearly different from a surname-based username).
4. Save the patron.
5. Observe on the patron details page that Username is auto-generated
   (not the value entered in quick add).
6. Apply this patch.
7. Repeat steps 2-4 with a new patron.
8. Verify on the patron details page that Username now matches the
   value entered in quick add.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:13 -07:00
Marcel de Rooy 0cc3190122
Bug 36752: Remove confusing TODOs
As Jan Kissig correctly mentioned on the BZ report, the various
summary info on charged items etc. are included.
This is done via the summary_info sub.

Note that the TODOs about
    BZ FID_HOLD_ITEMS_LMT
    CA FID_OVERDUE_ITEMS_LMT
    CB FID_CHARGED_ITEMS_LMT
are still valid. This is not included yet.

Test plan.
Since we are just removing comments, read the code.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:13 -07:00
Kyle M Hall 8959185536
Bug 41818: Add unit tests
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:12 -07:00
Jake Deery ab88c85f3b
Bug 41818: Add new Koha::Patron::Message method to SIP2
This bug adds Koha::Patron::Message::message_without_newlines, which has been added to C4::SIP::ILS::Patron in lieu of Koha::Patron::Message::message. The aim of this change is to prevent SIP2 units from crashing out when they encounter a patron message with newlines (\n or \r) in it, as units generally use a newline character to denote the end of a SIP2 transaction.

To test:
a)  go to /cgi-bin/koha/sip2/institutions
    *)  create an institution, name CPL, timeout 300, the rest default
b)  go to /cgi-bin/koha/sip2/accounts
    *)  create an account, userid koha, institution CPL, terminator crlf, the rest default
c)  go to /cgi-bin/koha/members/moremember.pl?borrowernumber=51
    *)  click add message in toolbar
    *)  enter some multi-line message e.g. test[RETURN]1[RETURN]2[RETURN]3[RETURN][RETURN][RETURN]
d)  run tail -f /var/log/koha/kohadev/sip-output.log, keep this window open
e)  run ./misc/sip_cli_emulator.pl -a 127.0.0.1 -p 6001 -su koha -sp koha -l CPL --patron 42 -m patron_information
f)  look at the tail log, notice how the MSG 64 line has a newline before the final pipe (|) character
== APPLY PATCH ==
g)  restart_all
h)  repeat steps e-f
i)  notice how in the tail log, the MSG 64 line now has the final pipe character contiguously placed with the successive characters
== SIGN OFF ==

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:12 -07:00
Lucas Gass 0dc0da7a3b
Bug 41417: Fix client variable in cataloging.js
To test:
1. Find or create a record in Kkoha.
2. From the record detail page click "Edit" and "Manage items"
3. Begin adding a new item
4. For any of the dropdowns that contain an authorized value ( LOST, DAMAGED, WITHDRAWN, CCODE, LOC ) click on the dropdown and begin typing an AV that doesn't exist.
5. You should see the text (select to create) appear, click it.
6. Now a modal appears "Create a new authorized value"
7. Fill out the required parts and click 'Save'.
8. Kaboom, 500 error:
9. APPLY PATCH, clear browser cache.
10. Try again, this time it should work.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:11 -07:00
Nick Clemens d7028939da
Bug 41393: Set the correct reply to address for advance notices
This patch additionally fetches the reply to address for advance notices

To test:
1 - Have some issues that will be due soon for a patron(s)
    Checkout with the due date set to tomorrow
2 - Verify the patron(s) have an email defined, verify the issuing branch has a reply to set
3 - Verify the patron(s) have messaging preference set to receive Advance notices by email 1 day in advance
4 - perl misc/cronjobs/advance_notices.pl -v -c
5 - Check the message queue
    SELECT * FROM message_queue WHERE borrowernumber={##}
6 - Note no rpely to is set
7 - Apply patch
8 - perl misc/cronjobs/advance_notices.pl -v -c
9 - Check the message queue
10 - Confirm newer message has the reply to populated
11 - Ensure the issued item has a different homebranch with a reply to address se
12 - perl misc/cronjobs/advance_notices.pl -v -c --frombranch item-homebranch
13 - Confirm message has correct reply to
14 - Set patron to want digest in messaging preferences
15 - Generate notices
16 - Confirm digets has the right reply to

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Tidied inline
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:11 -07:00
Kyle M Hall 081a139904
Bug 41360: Add toolbar of buttons for batch modifications to transport cost matrix
We can add a toolbar of buttons to
A) batch enable all cells
B) batch populate empty cells with a value
C) batch disable empty cells

Test plan:
1) Apply this patch
2) Visit the TCM editor
3) Try out the 3 new buttons, verify they behave correctly

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-02-26 09:04:10 -07:00
Marcel de Rooy b8742964db
Bug 28528: (QA follow-up) Remove "Koha 3" reference
Thats quite old :)

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:10 -07:00
Roman Dolny 8ec1b421bf
Bug 28528: bulkmarcimport delete option doesn't delete biblio_metadata
When using bulkmarcimport with the -d switch should not only delete
biblio, biblioitems and items, but also biblio_metadata.

I haven't checked if a FK takes care of the data, but if it does,
we should still also reset the AUTO_INCREMENT:...

Test plan:
==========
1. Check (and notice) the number of records in the biblio (bCount) and
   biblio_metadata (bmCount) tables. Check the AUTO_INCREMENT value
   for these tables (bAI, bmAI).
2. Import bibliographic records with delete, turn off foreign key checks
   and non-existent filename:
   misc/migration_tools/bulkmarcimport.pl -b -d -fk -file=non-exist-file
==> "Could not open..." is OK, file doesn't exist.
3. Repeat p. 1 and compare with previously noticed:
   bCount => 0 (OK)
   bmCount => unchanged (NOT OK)
   bAI => 1 (OK)
   bmAI => unchanged (NOT OK)
4. Apply the patch.
5. Repeat p. 2-3:
   bCount => 0 (OK)
   bmCount => 0 (changed, OK)
   bAI => 1 (OK)
   bmAI => 1 (changed, OK)

Sponsored-by: Ignatianum University in Cracow
Signed-off-by: Janusz Kaczmarek <januszop@gmail.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:09 -07:00
Hammat Wele 7b54b74b36
Bug 40787: Plugins buttons misaligned when search box is enabled
The buttons in the plugins home page ("Upload plugin" and "View plugins by class") become misaligned when you have the git repository search bar.

To test:

1. Add a github/gitlab repository in koha-conf.xml
  - In ktd shell, open /etc/koha/sites/kohadev/koha-conf.xml
  - Somewhere under <config> insert the following (or your own)

<plugin_repos>
  <repo>
   <name>Solutions inLibro</name>
   <org_name>inLibro</org_name>
   <service>github</service>
  </repo>
</plugin_repos>

  - Save the file
  - restart_all

2. Go to Administration > Manage plugins
--> Buttons are misaligned

3. Apply the patch

4. Run command « yarn build »

5. Repeat step 2
--> buttons are aligned nicely

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-26 09:04:02 -07:00
Jake Deery 7a0113e044
Bug 41619: (Follow-up) Consider Logical Defined-Or in test
This patch fixes xt/author/Text_CSV_Various.t by using a regex that can consider logical defined-or statements (|| and //, etc) when checking which formula has been used.

TO TEST:
a)  run prove -vv xt/author/Text_CSV_Various.t
    *)  notice the test fails
== APPLY PATCH ==
b)  repeat step a)
    *)  notice the test now passes
c)  inspect diff for xt/author/Text_CSV_Various.t
    *)  notice we are now using a wildcard (.*) between => and the first ' to skip variable declarations
d)  inspect diff for Koha/CSV.pm
    *)  notice we are now hard-coding 'empty' as a defined-or in case someone decides not to define a format at the class level
        (which was not being done before anyway and is now defaulted to undefined)
== SIGN OFF ==

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 12:47:44 -07:00
Pedro Amorim fb921843ab
Bug 41916: Update Cypress tests
Button is now 'Save' not 'Submit' after bug 40191

On ktd:
cypress run --spec t/cypress/integration/SIP2/*

All tests must pass

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 08:47:35 -07:00
Pedro Amorim 6f137822f7
Bug 41916: Clean-up obsolete code
No longer needed after bug 40191, but because both that and bug 37893 went in close to each other, this remained.
Removing this fixes the toolbar buttons on the SIP2 account form and fixes the console warning that was causing a failure in cypress tests

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 08:47:22 -07:00
Marcel de Rooy 47d88a6429
Bug 41097: (QA follow-up) Clarify POD
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:37:04 -07:00
Nick Clemens 7a5fa4cd50
Bug 41097: Don't use original record twice
This patch prevents us from passing the same record twice into the choose subs
to avoid a situation where the original record is not chosen as the one to keep
and we try to merge/delete the same record twice

To test:
1 - Run the script on a test system that you can reset
    perl misc/maintenance/dedup_authorities.pl -v -v --confirm -a="PERSO_NAME"
2 - Note duplication of entries in the output like:
    Merging 1660,1662 into 1660.
    Updated 0 biblios
    Deleting 1662
    Merge done.
3 - Note how many records are merged
4 - Run again to confirm nothing is merged, i.e. the script removed the dupes
5 - Reset your DB
6 - Apply patch
7 - Run the script again
8 - Note entries no longer have duplicated ids:
    Merging 1662 into 1660.
9 - Note the same number of records are merged
10 - Run again and confirm nothing is merged
11 - Sign off!

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-02-25 07:37:04 -07:00
Kyle M Hall 791648b643
Bug 41811: SIP server will inadvertently remove non-alphanumeric characters from the end of a message
If a message ends in a non-alphanumeric character, that character is stripped from the message.

For example:
6300120260210    182641          AOMPL|AA23529000035676(|

64              00120260210    182831000000000000000000000000AOCPL|AA23529000035676|AEHenry Acevedo|BLY|BHUSD|BV0|CC5|BD4345 Library Rd. Springfield, MA 44224|BF(212) 555-1212|PB19580530|PCS|PIY|AFGreetings from Koha. |

versus:
6300120260210    182641          AOMPL|AA23529000035676(|ACkoha|

64YYYY          00120260210    182835000000000000000000000000AOCPL|AA23529000035676(|AE|BLN|AFInvalid cardnumber|

The former should be returning the same "Invalid cardnumber" message the latter does.

Test Plan ( assumes you are using KTD ):
1) Install telnet: sudo apt install -y telnet
2) telnet 127.0.0.1 6001
3) Log in with: 9300CNkoha|COkoha|CPMPL|
4) Send the patron information request: 6300120260210    183346          AOMPL|AA23529000035676(|
5) Note you get a patron back
6) Apply this patch
7) Restart SIP!
8) Repeat steps 2-5
9) Noe you doe not get a patron back in the response!

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-02-25 07:37:03 -07:00
Pedro Amorim 86d36eacc0
Bug 41857: Only find singly suggestion by id if 'edit_form'
All ops that iterate on editsuggestions can potentially have multiple suggestionid and don't need stored_suggestion
cud-update_status
cud-delete
cud-archive
cud-unarchive
cud-update_itemtype
cud-update_manager

I'm unsure if op = 'else' should also be included with edit_form

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:37:03 -07:00
Pedro Amorim 3eaaf784cb
Bug 41857: Use suggesteddate_from and suggesteddate_to
From URL params. I believe this was an overupdate from bug 39721.

Test plan:
1) Create a new suggestion:
http://localhost:8081/cgi-bin/koha/suggestion/suggestion.pl?op=add_form&branchcode=
2) Add a title and submit.
3) From the table, select the suggesiton (check the box).
4) Click the 'submit' under 'Update manager' or 'Delete selected'.
5) Error

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:37:02 -07:00
Pedro Amorim 0ff67af58a
Bug 41857: Remove suggestion
I don't understand why we need this here, but it seems to be picking up a leftover suggestion from a previous loop.
This code itself is not within a loop, so it doesnt have the hashref containing suggestiontype from suggestion.pl.
It's trying to access suggestiontype from Koha::Suggestion which causes an explosion

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:37:02 -07:00
Nick Clemens 109e0d973d
Bug 38384: (follow-up) use Koha::Database in C4::Context
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:37:01 -07:00
Nick Clemens fabaa84003
Bug 38384: (follow-up) Improve tests for caching
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:37:01 -07:00
Nick Clemens cc8b13a11c
Bug 38384: (QA follow-up) Restore verbose option
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:37:00 -07:00
Nick Clemens 7cdb17cc2f
Bug 38384: (follow-up) move require Koha::Plugins to use
remaning ocurrences in test files seem okay, this covers the others

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:37:00 -07:00
Nick Clemens 84e0caae64
Bug 38384: (follow-up) Move loading into Koha::Plugins BEGIN block
Thinking about this today, we really just need the plugins initialized anytime we are using Koha::Plugins

Moving the loading into the BEGIN there seems to resolve the issue

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:59 -07:00
Martin Renvoize e091b0d4bc
Bug 38384: Refactor plugin loading to use lightweight Koha::Plugins::Loader
This patch addresses the circular dependency issues and code quality
concerns identified in QA by introducing a clean architectural solution.

Changes:

1. Created Koha::Plugins::Loader - A lightweight plugin loader with
   minimal dependencies that can be safely called during early
   initialization. Uses DBIx::Class when available for proper
   transaction support, falls back to raw DBI for early init.

2. Added Koha::Database::table_exists() - Consolidated table checking
   logic into a reusable method that can be called as both class and
   instance method.

3. Simplified C4::Context BEGIN block - Reduced from ~120 lines to
   ~13 lines (91% reduction). Removed XML::Simple dependency and
   manual database connection code.

4. Updated Koha::Plugins::get_enabled_plugins() - Now delegates to
   the Loader module for consistency.

5. Updated C4::Installer::TableExists - Delegates to the new
   Koha::Database::table_exists method.

This addresses all QA concerns:
- Eliminates XML::Simple usage (David Cook)
- Uses Koha::Database instead of manual DBI (David Cook)
- Reuses table checking logic (Nick Clemens)
- Eliminates circular dependencies (Paul Derscheid)
- Much cleaner and maintainable code

Test plan:
1. Run tests:
   prove t/db_dependent/Koha/Database.t
   prove t/db_dependent/Koha/Plugins/Loader.t
   prove t/db_dependent/Koha/Plugins/Plugins.t
2. All tests should pass
3. Install and enable a plugin via the UI
4. Verify plugin hooks are called correctly
5. Verify no regressions in plugin functionality

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:59 -07:00
Paul Derscheid 6dd32161af
Bug 38384: Prevent initialization failure by checking for existence of plugin_data first
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:58 -07:00
Paul Derscheid f0e223bfd9
Bug 38384: Inline plugin loading into C4::Context's BEGIN block
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:58 -07:00
Kyle M Hall 654556247f
Bug 38384: Trigger plugin modules loading as soon as possible in Koha
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:57 -07:00
Andrew Fuerste Henry 578b25404d
Bug 41848: Correct "invoide" to "invoice"
To test:
1 - try to receive an item, get an error loading items to receive
2 - apply patch, restart
3 - reload page, it works!

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:57 -07:00
Owen Leonard 16caf3cc6b
Bug 41040: (follow-up) Move JS to global JS file
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:56 -07:00
Owen Leonard 44bec3f9f3
Bug 41040: (follow-up) Tooltip text change and remove unnecessary class
- I think "Please enter a card number or name" sounds friendlier
- We don't add a "btn-primary" class to these buttons

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:56 -07:00
Kyle M Hall 3c403e2331
Bug 41040: Use tooltip for message instead of alert
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-02-25 07:36:55 -07:00
Kyle M Hall 4fe4809518
Bug 41040: Prevent form submission if no filters have been selected
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-02-25 07:36:55 -07:00
Kyle M Hall 222e3161cc
Bug 41040: Empty patron search from the header should not trigger a patron search
If the "arrow" button on the patron search is clicked with no value in
the textbox, it redirects to members/members-home.pl and performs a
search with no value automatically. It makes more send for it to behave
as the "Patrons" link in the navbar that directs to members-home.pl
where the librarian can then perform a more advanced search.

Test Plan:
1) Click the "Search patrons" link on the header, then the arrow button
2) Note an empty patron search is performed immediately
3) Apply this patch
4) Restart all the things!
5) Repeat step 1
6) Note a paton search is no longer triggered automatically

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-02-25 07:36:54 -07:00
Slava Shishkin fe51e9c881
Bug 41055: add missing accesskey attribute for print button
This commit adds the missing accesskey attribute to the print button in the circulation template.

Test plan:
1. Try to checkout an item that is waiting for pickup.
2. Try to print the checkout receipt using the alt+shift+p keyboard shortcut.
3. Verify that nothing happens when using the shortcut.
4. After applying this commit, try the same steps again.
5. Verify that the print button now works with the alt+shift+p keyboard shortcut.

Sponsored-by: Koha-Suomi Oy

Signed-off-by: Slava Shishkin <slavashishkin@gmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:54 -07:00
Martin Renvoize fe791731b4
Bug 41679: Fix overlapping modals when hold conflicts with stock rotation transfer
When checking in an item that has both a hold and a stock rotation
repatriation transfer, both modals would appear simultaneously,
overlapping each other and making it impossible to interact with
either one properly.

This occurred because:
1. The hold modal showed when 'waiting' was set
2. The transfer modal condition checked for 'reserved' but not 'waiting'
3. Both modals had the "block" class, causing simultaneous display

This patch ensures holds take precedence over stock rotation transfers
by adding a check for the 'waiting' flag to the transfer modal condition.

When a hold is found, only the hold modal displays. The stock rotation
transfer can be handled at a subsequent check-in, which is appropriate
since holds are patron-facing and time-sensitive, while stock rotation
is an internal process that can be deferred.

Test plan:
1. Enable StockRotation system preference
2. Navigate to Cataloging->Stock rotation
3. Click '+ New rota' and fill out with any values
4. Click 'Manage->stages' for the Rota
5. Add a stage for Centerville for 1 day, and Midway for 1 day
6. Return to rotas
7. Click Manage->items for the rota
8. Add an item
9. Return to rotas
10. Click 'Activate'
11. On the command line run the stock rotation job:
    perl misc/cronjobs/stockrotation.pl -x
12. There should be one item for initiation
13. Run it again, there should be one stationary item
14. Place an item specific hold for the item, for pickup at Midway
15. Check the item in at Centerville, confirm transfer
16. Check the item in at Midway, confirm hold
17. Run the stock rotation job again:
    perl misc/cronjobs/stockrotation.pl -x
18. There should be one item for repatriation
19. Check in the item again at Midway
20. Before patch: Two overlapping modals appear (hold + transfer)
21. After patch: Only the hold modal appears
22. Confirm the hold - no transfer modal should appear
23. The stock rotation transfer will be handled at the next check-in

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:53 -07:00
Jonathan Druart 399085e092
Bug 41591: Remove view=1 parameter from the invoices table
It has been removed in the controller by the previous patch.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:53 -07:00
David Cook 605388852d
Bug 41591: Prevent inline rendering of invoice files
This patch forces download of invoice files rather than showing
them inline where they can potentially perform XSS

Test plan:
0. Apply the patch
1. koha-plack --restart kohadev
2. Find or create an active vendor
3. Create a basket if one doesn't already exist
4. Receive a shipment for the vendor
5. Create an invoice
6. Go to the invoice
7. In the URL change invoice.pl to invoice-files.pl
(or turn on AcqEnableFiles and go to it that way)
8. Upload an image file (e.g. SVG) or PDF
9. Click on the filename
10. Note that instead of displaying in the browser,
the file will be downloaded

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-25 07:36:46 -07:00
Pedro Amorim 5a625ba45e
Bug 38096: (QA follow-up): Add URLLinkText to opac xsl also
Fix error:
[2026/02/11 09:18:00] [WARN] XSLT::Base: runtime error: file /usr/share/koha/opac/htdocs/opac-tmpl/bootstrap/en/xslt/MARC21slimUtils.xsl line 726 element choose
Variable 'URLLinkText' has not been declared.
Undefined variable
 at /usr/share/koha/lib/Koha/XSLT/Base.pm line 231.

Run tests to prove the patch fixes the issue (this fails before the patch):
prove t/db_dependent/Search.t

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-13 07:23:45 -07:00
Jonathan Druart a5d9f920d0
Bug 41563: (follow-up 2) Tidy parcel.tt
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-12 07:58:49 -07:00
Jonathan Druart b714b4b8f9
Bug 41831: Mock POST /packages in Dialog_spec.ts
ERM/Titles_spec.ts fails if run after ERM/Dialog_spec.ts because Dialog's "...created!" test create a package and does not delete it.

02:48:52 koha-1      |       <failure message="Timed out retrying after 10000ms: Expected to find content: &apos;There are no packages created yet&apos; within the element: &lt;fieldset#resources_relationship.rows&gt; but never did." type="AssertionError"><![CDATA[AssertionError: Timed out retrying after 10000ms: Expected to find content: 'There are no packages created yet' within the element: <fieldset#resources_relationship.rows> but never did.

Test plan:
0. Do not apply the patch
1. yarn cypress run --spec t/cypress/integration/ERM/Dialog_spec.ts,t/cypress/integration/ERM/Titles_spec.ts
=> FAIL
2. Notice that one entry has been created in the DB table erm_eholdings_packages
3. Delete the DB entry:
  sudo koha-mysql kohadev -e 'DELETE FROM erm_eholdings_packages'
4. Apply this patch
5. Repeat 1
=> SUCCESS
6. Notice that no new entry has been created in erm_eholdings_packages

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-12 07:58:48 -07:00
Jonathan Druart f20fde6565
Bug 38262: Fix VueFramework_spec.ts
Fix the following error:
 Error: The following error originated from your application code, not from Cypress. It was caused by an unhandled promise rejection.

  > JS Warning detected: "[Vue warn]: Unhandled error during execution of watcher callback" "\n" " at <AdditionalFieldsEntry"

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-12 07:58:40 -07:00
Jonathan Druart a019e78f4b
Bug 41812: Adjust find-missing-csrf.t test to ignore csrf input from JS
This code injected the csrf token using the TT include file, that made
pass xt/find-missing-csrf.t

This bug removed the TT tags from the script tags, and we now need to
use a JS variable and retrieve the csrf token from the meta tag.

This patch suggests to adjust the script to ignore this case. We could
also rewrite the whole JS code and use built the HTML elements using
vanilla JS but that's for another day...

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 15:51:54 -07:00
Lucas Gass 6adf1c1cc1
Bug 28530: (follow-up) Place system pref in corret order
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 15:48:35 -07:00
Martin Renvoize ec0453354c
Bug 38096: (follow-up) Fix missing biblionumber parameter in renderMARCOnlineResourceField
The renderMARCOnlineResourceField template uses $biblionumber for the
TrackClicks feature but this parameter was never declared in the template
definition nor passed by any of the call sites.

This caused XSLT runtime errors when TrackClicks system preference is
enabled ('track' or 'anonymous') and records contain MARC fields 856 or
857 with subfield $u (URLs):

  Variable 'biblionumber' has not been declared.
  Undefined variable
  XPath evaluation returned no result.

This patch:
1. Adds biblionumber parameter to renderMARCOnlineResourceField template
2. Passes biblionumber value in all 4 call sites (2 in Detail, 2 in Results)

Test plan:
1. Enable TrackClicks: System Preferences > OPAC > TrackClicks = "track"
2. Find a bibliographic record with field 856 or 857 containing subfield $u
3. Run: yarn build (to regenerate XSLT for all languages)
4. View the record in OPAC detail and search results pages
5. Verify no XSLT errors in plack-opac-error.log
6. Verify online resource links display correctly
7. Verify links include biblionumber parameter in tracklinks.pl URLs

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 15:48:35 -07:00
Pedro Amorim f63a1d295f
Bug 38096: (QA follow-up): Fix URLLinkText
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 15:48:28 -07:00
Jan Kissig 18c18175ca
Bug 41120: Fix broken Add data provider functionality
Clicking on New data provider in the ERM eUsage section will not work, when a data provider was shown before.

Test plan:

prequisites:
- activate the ERM module system preference.
- this is for ktd

a) go to /cgi-bin/koha/erm/eusage/usage_data_providers
b) add a new data provider, dummy data is sufficient
c) submit the new provider and on the following screen click close to return to the provider list
d) click on 'Add data provider' (open web dev tools will display an error)

e) apply patch
f) run 'yarn js:build'
f) do a hard refresh in the browser
g) on provider list click your created provider
h) close it via 'close' to return to the provider list
d) click on 'Add data provider' should work now

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:52:04 -07:00
Olli Kautonen 9445ffc9aa
Bug 40154: prevent deletion of items with holds
This patch prevents deletion of items that have item level holds on
them.

Test plan:
1. Create an item level hold.
2. Delete the item using alternative A or B:
   A. Open the biblios Detail-view -> From the toolbar, open
      "Edit"-dropdown menu -> Manage items -> Delete the item that has
      an item level hold attached to it from "Actions"-dropdown menu
   B. Open the biblios Detail-view -> Select the item that has an item
      level hold attached to it -> Click "Delete selected items" ->
      Confirm deletion
3. Notice that the item is deleted without any warning and the item
   level hold has disappeared.
4. Apply this patch.
5. Repeat steps 1-3.
6. Notice that the item cannot be deleted and a warning is shown.
7. prove t/db_dependent/Koha/Item.t
8. Run the REST API tests: prove t/db_dependent/api/v1/items.t

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:52:04 -07:00
Jonathan Druart 57dbd3677a
Bug 41565: bookings - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:52:03 -07:00
Jonathan Druart 7a80a9be01
Bug 41565: Tidy kohaTable - bookings/list.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:52:03 -07:00
Owen Leonard a5dc152afc
Bug 41564: (follow-up) Minor fixes
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:52:02 -07:00
Jonathan Druart 22087ab2ec
Bug 41564: admin - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:52:02 -07:00
Jonathan Druart 8ed37667b8
Bug 41564: Tidy kohaTable - admin/z3950servers.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:52:01 -07:00
Jonathan Druart 2c4923e9ff
Bug 41564: Tidy kohaTable - admin/systempreferences.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:52:01 -07:00
Jonathan Druart 9d5b3885ab
Bug 41564: Tidy kohaTable - admin/smtp_servers.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:52:00 -07:00
Jonathan Druart 724ca164e8
Bug 41564: Tidy kohaTable - admin/oai_servers.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:52:00 -07:00
Jonathan Druart 9124479652
Bug 41564: Tidy kohaTable - admin/marc-overlay-rules.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:59 -07:00
Jonathan Druart 3d24965157
Bug 41564: Tidy kohaTable - admin/localization.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:59 -07:00
Jonathan Druart f243cf8197
Bug 41564: Tidy kohaTable - admin/itemtypes.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:58 -07:00
Jonathan Druart aa255653d1
Bug 41564: Tidy kohaTable - admin/identity_provider_domains.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:58 -07:00
Jonathan Druart 3435a23656
Bug 41564: Tidy kohaTable - admin/file_transports.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:57 -07:00
Jonathan Druart 3206232bd8
Bug 41564: Tidy kohaTable - admin/edi_accounts.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:57 -07:00
Jonathan Druart 87d5ac14df
Bug 41564: Tidy kohaTable - admin/currenty.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:56 -07:00
Jonathan Druart f5eb9a4d1a
Bug 41564: Tidy kohaTable - admin/cities.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:56 -07:00
Jonathan Druart cf8c0b6b5d
Bug 41564: Tidy kohaTable - admin/branches.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:55 -07:00
Jonathan Druart 50094e9fb2
Bug 41564: Tidy kohaTable - admin/branch_transfer_limits.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:55 -07:00
Jonathan Druart 6d6158070b
Bug 41564: Tidy kohaTable - admin/background_jobs.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:54 -07:00
Jonathan Druart 5700336366
Bug 41564: Tidy kohaTable - admin/auth_subfields_structure.tt
See bug 41324 comment 6

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:54 -07:00
Jonathan Druart 288d4b28ab
Bug 41564: Tidy kohaTable - admin/aqbudgets.tt
See bug 41324 comment 6

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:53 -07:00
Jonathan Druart 8e613bc268
Bug 41564: Tidy kohaTable - admin/aqbudgetperiods.tt
See bug 41324 comment 6
1. Variable serialization

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:53 -07:00
Jonathan Druart ebff6e124d
Bug 41563: Remove JS error from newordersuggestion
Not caused by this patchset but it's a good place to fix it (and not add
a conflict)

Uncaught TypeError: can't access property "DataTable", suggestionst is undefined

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:52 -07:00
Owen Leonard d45c6ce115
Bug 41563: (follow-up) Tidy parcel.tt
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:52 -07:00
Owen Leonard 39d349e1f1
Bug 41563: (follow-up) Fix two typos
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:51 -07:00
Jonathan Druart a0fdafe8ff
Bug 41563: acqui - auto tidy
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:51 -07:00
Jonathan Druart 5163b05ad6
Bug 41563: Tidy kohaTable - acqui/vendor_issues.tt
See bug 41324 comment 6
1. Variable serialization

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:50 -07:00
Jonathan Druart bd66dff065
Bug 41563: Tidy kohaTable - acqui/parcel.tt
See bug 41324 comment 6
1. Variable serialization
2. Use of permissions and sysprefs

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:50 -07:00
Jonathan Druart bb40a690d2
Bug 41563: Tidy kohaTable - acqui/orderreceive.tt
See bug 41324 comment 6
1. Variable serialization
2. Use of permissions and sysprefs

We also removed 'UniqueItemFields' from the controller.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:49 -07:00
Jonathan Druart e99cdb29f2
Bug 41563: Tidy kohaTable - acqui/newordersuggestion.tt
See bug 41324 comment 6
1. Variable serialization

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:49 -07:00
Jonathan Druart a5989d0832
Bug 41563: Tidy kohaTable - acqui/lateorders.tt
See bug 41324 comment 6
1. Variable serialization

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:48 -07:00
Jonathan Druart 68219779d0
Bug 41563: Tidy kohaTable - acqui/invoice.tt
See bug 41324 comment 6
1. Variable serialization

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:48 -07:00
Jonathan Druart e987e7f4c9
Bug 41563: Tidy kohaTable - acqui/histsearch.tt
See bug 41324 comment 6
1. Variable serialization

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:47 -07:00
Jonathan Druart 2289262dac
Bug 41563: Tidy kohaTable - acqui/duplicate_orders.tt
See bug 41324 comment 6
1. Variable serialization

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:47 -07:00
Jonathan Druart 6ca1854aa8
Bug 41563: Tidy kohaTable - acqui/basketgroup.tt
See bug 41324 comment 6
1. Variable serialization

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:46 -07:00
Jonathan Druart 5c67161e8f
Bug 41563: Tidy kohaTable - acqui/basket.tt
See bug 41324 comment 6
1. Variable serialization
3. "table_settings" variable

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:46 -07:00
Jonathan Druart dd0578f809
Bug 41563: Tidy kohaTable - acqui/acqui-home.tt
See bug 41324 comment 6
1. Variable serialization

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:45 -07:00
Jonathan Druart b708776eb0
Bug 41562: Introduce "stores" for regular javascript
We already have "Stores" in Vue, here we are going to use a shared
object to store permissions and sysrepfs

For follow-up bug reports we will have the need to store syspref's
values and permissions.

It seems a good idea to have a "store" to share the permissions and
system preferences' values we need for the current page. Several
"script" tag can coexist and define their own permissions or sysprefs.

Added on its own bug for reusability purpose.

Test plan:
Test follow-up bug reports ;)

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:45 -07:00
Lucas Gass fdf9fcfee4
Bug 28530: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:44 -07:00
Lucas Gass d427bd25c9
Bug 28530: DBRev 25.12.00.024
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:44 -07:00
Lucas Gass f2721c8be3
Bug 28530: (RM follow-up) Move kohastructure update to right place alphabetically
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:43 -07:00
Martin Renvoize 4a658430a3
Bug 28530: (QA follow-up) Fix branch transfer limits not checking alternate libraries
Fixed a pre-existing bug where branch transfer limits would prevent ANY
transfer if the optimal library was blocked, instead of trying the next
best candidate.

Original behavior:
- Sort candidates by best ratio
- Check if transfer to best candidate is allowed
- If not allowed: exit loop immediately (bug!) and return undef
- Result: No transfer created even if other valid candidates exist

Fixed behavior:
- Sort candidates by best ratio
- Check if transfer to best candidate is allowed
- If allowed: use that library and exit loop
- If not allowed: continue to next candidate
- Result: Uses the best ALLOWED candidate

This fix ensures that when branch transfer limits block the optimal
destination, the system will try the next-best option instead of
giving up entirely.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:43 -07:00
Martin Renvoize 7076f57bd8
Bug 28530: (QA follow-up) Fix N+1 query performance issue
Refactored lowest_ratio_library() to eliminate the N+1 query problem
by using aggregated queries with GROUP BY instead of per-branch queries.

Performance improvement:
- Before: O(3n) queries where n = number of branches with float limits
  Example: 20 branches = 60 database queries
- After: O(3) queries regardless of number of branches
  Example: 20 branches = 3 database queries

The optimization uses three aggregated queries:
1. Single query with GROUP BY to get item counts at all branches
2. Single query with GROUP BY to get in-transit-to counts for all branches
3. Single query with GROUP BY to get in-transit-from counts for all branches

Results are combined in memory to calculate ratios, maintaining identical
logic and behavior while dramatically improving database efficiency.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:42 -07:00
Martin Renvoize 30fd316872
Bug 28530: (QA follow-up) Add POD documentation and database index
- Add comprehensive POD documentation for lowest_ratio_library() method
  including parameter descriptions, return values, and algorithm explanation
- Add database index on itemtype column in library_float_limits table
  to improve query performance when searching by itemtype

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:42 -07:00
Martin Renvoize 3ee2efa9d0
Bug 28530: (QA follow-up) Use DBIC in controller
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:41 -07:00
Martin Renvoize 24afe8e36e
Bug 28530: (QA follow-up) Guard against divide by zero
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:41 -07:00
Martin Renvoize 600bfe072d
Bug 28530: (QA follow-up) Remove unused LibraryFloatLimitTransferRequest message handling
The LibraryFloatLimitTransferRequest message was never set in C4::Circulation,
making this code unreachable. Float limit transfers are already properly handled
through the existing transfer message system (WasTransfered/NeedsTransfer) with
TransferTrigger set to 'LibraryFloatLimit'.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:40 -07:00
Lucas Gass c56429b29d
Bug 28530: Add missing use statement for Koha::Library::FloatLimits
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:39 -07:00
Lucas Gass 06b352a32a
Bug 28530: Fix spelling mistakes
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:39 -07:00
Lucas Gass 336d48eae2
Bug 28530: Fix forbidden http pattern
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:38 -07:00
Lucas Gass b2e1993597
Bug 28530: Fix tests
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:38 -07:00
Lucas Gass e67a210b40
Bug 28530: Adjust tests
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:37 -07:00
Lucas Gass 026da298f4
Bug 28530: Increment and decrement item count before calculation
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:37 -07:00
Lucas Gass c39350dfb2
Bug 28530: Add more tests
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:36 -07:00
Lucas Gass ef899660a0
Bug 28530: If return policy is set to float in library group, remove non group branches
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:36 -07:00
Lucas Gass 1f63d8c13f
Bug 28530: Implement float limits
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:35 -07:00
Kyle M Hall 83e6aecd07
Bug 28530: Add unit tests
Signed-off-by: Andrew Fuerste-Henry <andrewfh@dubcolib.org>
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:35 -07:00
Kyle M Hall be2cbf15de
Bug 28530: Add float limits editor
Signed-off-by: Andrew Fuerste-Henry <andrewfh@dubcolib.org>
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:34 -07:00
Kyle M Hall a6a446ad39
Bug 28530: Add Koha Object(s) for Float Limits
Signed-off-by: Andrew Fuerste-Henry <andrewfh@dubcolib.org>
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:34 -07:00
Kyle M Hall cababbbd87
Bug 28530: Update database, add new schema file
Signed-off-by: Andrew Fuerste-Henry <andrewfh@dubcolib.org>
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Kris Becker <kbecker@jcls.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:33 -07:00
Kyle M Hall 556bd55543
Bug 33782: (QA follow-up) Fix qa issues
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:33 -07:00
Tomás Cohen Arazi 13e8f6105a
Bug 33782: (follow-up) Remove code duplication in OAuth client
This patch eliminates duplicated patron search logic in the
_get_data_and_patron method by extracting it into a dedicated
helper method _find_patron_by_matchpoint.

Changes:
- Extract duplicated patron search logic (8 lines removed)
- Add _find_patron_by_matchpoint helper method
- Improve code maintainability and readability
- No functional changes - behavior preserved

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Auth/Client/OAuth.t
=> SUCCESS: Tests pass! OAuth client tests work correctly
  k$ prove t/db_dependent/api/v1/oauth.t
=> SUCCESS: Tests pass! OAuth API integration works
3. Confirm OAuth authentication still works in browser
4. Sign off :-D

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:32 -07:00
Tomás Cohen Arazi 4ff2eb3469
Bug 33782: Add unit tests for Koha::Auth::Client::OAuth
This patch adds comprehensive unit tests for the OAuth authentication
client, specifically covering the _get_data_and_patron method which
handles JWT token processing and UserInfo endpoint integration.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Auth/Client/OAuth.t
=> SUCCESS: Tests pass!
3. Tests cover:
   - JWT id_token processing and patron matching
   - UserInfo endpoint calls and data merging
   - Non-existent user scenarios
   - Combined JWT + UserInfo data sources
4. Sign off :-D

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:32 -07:00
Slava Shishkin b16ff5a8a0
Bug 41458: Pass a valid GID to Net::Server for SIP
When starting SIP, Koha passes an incorrect value to the
Net::Server "group" parameter, which can generate an
error in the logs":
  Couldn't become gid "<uid>": Operation not permitted

This change ensures that a valid numeric GID is passed instead.

Test plan:
1. Enter KTD shell.
2. To reproduce the error: sudo groupmod -g 8001 kohadev-koha
3. Restart SIP: sudo koha-sip --restart kohadev
4. Check SIP logs:
     /var/log/koha/kohadev/sip-output.log
5. Observe the error:
     Couldn't become gid "<uid>": Operation not permitted
6. Apply this patch.
7. Repeat steps 2 to 4.
8. Verify that the "Couldn't become gid" error is no longer logged.

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:31 -07:00
Pedro Amorim 52d538312e
Bug 38096: (QA follow-up): Fix indentation
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:31 -07:00
Pedro Amorim 19c04d4e7c
Bug 38096: (QA follow-up): Fix passing string param to template
Upon following original test plan after rebase, noticed that
OPACDisplay856uAsImage wasn't working for 'Details' or 'Results'.
The culprit was how each screen is invoking the template, passing the
string param in a wrong XSL way.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:30 -07:00
Pedro Amorim 4b78e0ed3a
Bug 38096: Staff: Display 857 to detail and results pages
This work DRYs the 856 display logic to renderMARCOnlineResourceField.
This was previously written in detail and results pages.

Once this single function exists, 857 can also make use of it to be
displayed using the same logic/features already present for 856.

1) If testing k-t-d, add the test data for test plan (otherwise prep instructions below):
updatedatabase
2) Force reindex of this biblio by saving it, visit the biblio:
<staff_url>/cgi-bin/koha/catalogue/detail.pl?biblionumber=76
3) Click "Edit" -> "Edit record" -> "Save"
4) Open the following URL's, preferably on 4 different browser tabs (we're using biblio 76 for this test):
<staff_url>/cgi-bin/koha/catalogue/search.pl?q=music (first record [76] is the one we're testing)
<staff_url>/cgi-bin/koha/catalogue/detail.pl?biblionumber=76
<opac_url>/cgi-bin/koha/opac-search.pl?idx=&q=music (first record [76] is the one we're testing)
<opac_url>/cgi-bin/koha/opac-detail.pl?biblionumber=76
5) Verify that on all pages there are 3 instances of 856 (Online resources) and 3 instances of 857 (Online archive resources) with their respective link labels and URLs.
6) Click on the "Koha community no protocol" link for 856 and 857, notice the 'http://' protocol is added.
7) Change the values of OPACDisplay856uAsImage + Display856uAsImage and refresh the respective page. Verify it works as expected (respects the system preference). Note: This only applies for 856, not 857 (out of scope here).
8) Change the value of OPACURLOpenInNewWindow and click on the links before and after. Verify they respect the system preference.

If not on k-t-d and updatedatabase not available:

perl /kohadevbox/koha/installer/data/mysql/updatedatabase.pl
or
koha-shell -c "perl /kohadevbox/koha/installer/data/mysql/updatedatabase.pl" <instance_name>

If CLI not available (preparation on UI):
A) Add field 857 to the 'Default' framework
B) Edit the field:
<staff_url>/cgi-bin/koha/admin/marc_subfields_structure.pl?op=add_form&tagfield=857&frameworkcode=
C) Check the 'Editor' checkbox for subfields q, u and z (Add these if not there already)
D) Edit the record in question, visit:
<staff_url>/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=76
E) Click 'Settings' dropdown and pick 'Default' framework.
F) Add MARC data that corresponds to the following:
3 instances of 856 as below. 3 instances of 857 as below.

  <datafield tag="856" ind1="4" ind2="2">
    <subfield code="z">Koha community no protocol</subfield>
    <subfield code="u">koha-community.org/</subfield>
  </datafield>
  <datafield tag="856" ind1="4" ind2="2">
    <subfield code="z">Koha community with protocol</subfield>
    <subfield code="u">https://koha-community.org/</subfield>
  </datafield>
  <datafield tag="856" ind1="4" ind2="2">
    <subfield code="z">Koha logo image</subfield>
    <subfield code="q">jpeg</subfield>
    <subfield code="u">https://koha-community.org/files/2013/09/cropped-kohabanner3.jpg</subfield>
  </datafield>
  <datafield tag="857" ind1="4" ind2="2">
    <subfield code="z">Koha community no protocol</subfield>
    <subfield code="u">koha-community.org/</subfield>
  </datafield>
  <datafield tag="857" ind1="4" ind2="2">
    <subfield code="z">Koha community with protocol</subfield>
    <subfield code="u">https://koha-community.org/</subfield>
  </datafield>
  <datafield tag="857" ind1="4" ind2="2">
    <subfield code="z">KohaCon 2024 logo image</subfield>
    <subfield code="q">jpeg</subfield>
    <subfield code="u">https://2024.kohacon.org/wp-content/uploads/2024/01/kohacon24_fr_verticale_vert_noir.png</subfield>
  </datafield>
G) At this point you're ready to start the original test plan from step 4)

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:30 -07:00
Pedro Amorim cb900b2d7b
Bug 38096: OPAC: Display 857 on detail and results pages
Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:29 -07:00
Pedro Amorim 911a04fc32
Bug 38096: OPAC: Move 856 logic to renderMARCOnlineResourceField utils
Now that OPAC detail and search results are consitent, we can DRY this
logic to its own function to be also utilized by 857 in the following
commit

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:29 -07:00
Pedro Amorim 1fa5a8d7a5
Bug 38096: OPAC: Preparation
The OPAC logic for 856 in results page and detail page has diverged.
This commit ensures the 856 in the OPAC detail and search results pages
are consistent, in preparation to become DRY in the following commits.

With this DRY refactor, the following divergences are now aligned:
- 856 block now calls AddMissingProtocol regardless of value OPACURLOpenInNewWindow (previously was only being called if OPACURLOpenInNewWindow was 0). Now it's in line with what exists in MARC21slim2OPACDetail.xsl
- substring(,1,6)='image/' is also now a consideration when OPACURLOpenInNewWindow is 1. (previously was only being called if OPACURLOpenInNewWindow was 0). This is now in line with that exists in MARC21slim2OPACDetail.xsl

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:28 -07:00
Paul Derscheid 4950884e17
Bug 41798: (QA follow-up) Add missing html filter
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:28 -07:00
Martin Renvoize a92e6ef8ea
Bug 41798: Fix FTP passive mode not saving and add display indicator
The FTP passive mode setting was not being saved due to a typo in the
parameter name ('passiv' instead of 'passive'). This fixes the typo
and adds display of passive mode status in the transports table.

Test plan:
1. Go to Administration > File transports
2. Click "New file transport"
3. Fill in the name (e.g., "Test FTP")
4. Change Transport to "FTP"
5. Set Passive mode to "Enabled (Recommended)"
6. Fill in required fields (host, username, etc.) and save
7. Edit the transport you just created
8. Verify Passive mode is still set to "Enabled" (not reverted to Disabled)
9. Return to the file transports list
10. Verify the Transport column shows "FTP (passive)" for this transport
11. Edit the transport and change Passive mode to "Disabled"
12. Save and return to the list
13. Verify the Transport column now shows just "FTP" (without the passive indicator)

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 10:51:21 -07:00
Martin Renvoize fd7cab9c41
Bug 3492: (follow-up) Replace GetReserveFee with holds_fee method
GetReserveFee was removed as part of the migration to circulation
rules-based hold fees, but reserve/request.pl still imported and
used it, causing compilation errors.

This patch replaces the two usages of GetReserveFee with the modern
approach using Koha::Item->holds_fee($patron), matching the
implementation in opac-reserve.pl.

The fee is calculated by iterating through available items and using
the first non-zero fee found, which provides backward compatibility
with the legacy reserve_charge template variable.

This implementation uses ReservesControlBranch to determine which
branch's circulation rules apply (either item home library or patron
home library), consistent with the existing Koha policy framework.

Test plan:
1. Apply patch
2. Verify reserve/request.pl compiles: perl -wc reserve/request.pl
3. Navigate to staff interface holds page with patron and biblio
4. Verify hold fee is displayed correctly if configured in
   circulation rules
5. Place a hold and verify fee is charged correctly

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:09 -07:00
Martin Renvoize 4b0fcd7e75
Bug 3492: (follow-up) Add missing TitleHoldFeeStrategy to sysprefs.sql
The TitleHoldFeeStrategy system preference was added in the database
update script (251200023.pl) but was missing from sysprefs.sql,
causing issues for fresh installations that don't run the update
script.

This preference likely disappeared during rebases. Adding it in the
correct alphabetical position between 'timeout' and
'todaysIssuesDefaultSortOrder'.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:09 -07:00
Paul Derscheid 9c7b02b365
Bug 37707: (QA follow-up) Fix incorrect bold date expectations in Cypress test
- The test for circulation rules date calculations did not account for
  the start date being bold, only the period endpoint dates
- place_booking.js explicitly adds the selected start date to boldDates
  (boldDates = [new Date(startDate)]), giving it the "title" class
- The verification loop checking for unexpected bold dates then failed
  when it encountered the start date, which was bold but not expected
- Add clearZoneStart to expectedBoldDates to match actual behavior

To test:
- yarn cypress run --spec t/cypress/integration/Circulation/bookingsModalDatePicker_spec.ts
- Verify that the "should handle circulation rules date calculations and visual feedback" test now passes

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:08 -07:00
Paul Derscheid 871b18bd47
Bug 37707: (QA follow-up) Fix grammatical error
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:08 -07:00
Paul Derscheid 5c24a2e3ff
Bug 37707: (QA follow-up) Fix typo in comment
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:07 -07:00
Martin Renvoize b591400ac1
Bug 37707: (follow-up) Add comprehensive Cypress tests for bookings modal
Reorganize and expand Cypress tests into three focused spec files:

bookingsModalBasic_spec.ts:
- Modal initialization and field interactions
- Form validation and submission
- Edit booking functionality
- 'Any item' booking with server-side item selection

bookingsModalDatePicker_spec.ts:
- Flatpickr initialization and constraints
- Date range validation
- Circulation rules (issue length, renewals)
- Lead/trail period conflict detection
- 'Any item' mode date availability
- Event dots for existing bookings

bookingsModalTimezone_spec.ts:
- Cross-timezone date display
- Multi-day booking spans
- Conflict detection with timezone awareness
- API round-trip date preservation
- Month boundary handling

Add shared test utilities to e2e.js for date helpers.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:07 -07:00
Martin Renvoize 0f5d2e457c
Bug 37707: (follow-up) Refactor place_booking.js for maintainability
Extract utility functions and reduce code duplication:

- Add toInt(), startOfDay(), datesOverlap(), isDateInRange() helpers
- Simplify containsAny() using Set and Array.some()
- Refactor isItemAvailableForPeriod() to use new utilities
- Refactor getAvailableItemsOnDate() and isDateDisabledForSpecificItem()
- Extract submit handler helpers: getAvailableItemsForPeriod(),
  buildBookingPayload(), createTimelineItem(), showBookingError(),
  showBookingSuccess(), refreshBookingsTable(), setPickerDates()
- Replace parseInt() calls with toInt() for consistency
- Simplify modal reset handler with jQuery method chaining

No functional changes - prepares codebase for future Vue conversion.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:06 -07:00
Martin Renvoize 70b53f7baf
Bug 37707: (follow-up) Fix timezone handling for bookings
Fix date handling issues that caused bookings to shift by a day
when the browser timezone differs from the server timezone:

- Normalize dates to browser timezone for calendar display
- Use dayjs without UTC conversion for bookingsByDate keys
- Extract local date and send as explicit UTC day boundaries
- Preserve user's selected DATE regardless of browser timezone

Timeline display:
- Convert server dates to library timezone for display
- Add $timezone() and $toDisplayDate() helper functions

This ensures that a booking for "June 15" displays and saves as
June 15 regardless of the user's timezone offset.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:06 -07:00
Martin Renvoize 6a17417359
Bug 37707: (follow-up) Support lead/trail periods for 'any item' bookings
When booking "any item of type X", lead/trail period handling must
consider that multiple items may be available:

- Track per-item conflicts instead of global conflicts
- Only disable dates when ALL items of the type have conflicts
- Find closest "all items booked" dates for visual feedback
- Use mathematical search (180 days) to detect conflicts across
  month boundaries

Visual feedback for 'any item' mode:
- Show lead/trail periods around "all items booked" dates
- These represent dates where no item of the type is available

Skip redundant class-based conflict checks since mathematical
detection already handles cross-month boundary cases.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:05 -07:00
Martin Renvoize 43f5f167d1
Bug 37707: Add bidirectional lead/trail period conflict detection
Enhance the booking modal to detect and prevent lead/trail period
conflicts in both directions:

- New booking's lead period vs existing booking's trail period
- New booking's trail period vs existing booking's lead period

Visual feedback:
- Highlight existing booking's lead periods (before booking starts)
- Highlight existing booking's trail periods (after booking ends)
- Add CSS classes for start/end boundaries with rounded corners

User feedback:
- Show contextual messages explaining why dates are blocked
- Differentiate between lead period, trail period, and booking conflicts
- Display informational messages when hovering available dates

The conflict detection uses mathematical date range comparisons that
work across month boundaries, not just visible calendar dates.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:05 -07:00
Lucas Gass 4858768f9a
Bug 40134: (RM follow-up) Tidy commit
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:04 -07:00
Martin Renvoize 9360ed6157
Bug 40134: Add Cypress tests for booking improvements
This patch adds end-to-end Cypress tests for the booking modal improvements,
including smart window maximization and server-side optimal item selection.

New Tests:

1. "should successfully submit an 'Any item' booking with server-side optimal item selection"
   - Tests the complete "any item" booking workflow
   - Selects itemtype and "Any item" option (item_id = 0)
   - Submits booking form
   - Verifies modal closes without errors
   - Validates booking created in database
   - Confirms item was assigned by server
   - Verifies server-side optimal selection completed
   - Test status: PASSING

2. "should maximize booking window by dynamically reducing available items during overlaps"
   - Creates 4 items with strategic booking patterns
   - Tests "never re-add items to pool" algorithm
   - Validates 5 scenarios:
     a) Full window when all items available
     b) Reduced window when Item 1 becomes unavailable
     c) Further reduction when Items 1&2 unavailable
     d) Minimal window with Items 1,2,3 unavailable
     e) No availability when all items booked
   - Demonstrates smart window maximization
   - Test status: PASSING

3. "should handle edit mode date initialization correctly"
   - Tests fix for edit mode race condition
   - Verifies dates populate correctly when editing booking
   - Ensures itemtype is set before date initialization
   - Tests that flatpickr dates are set properly
   - Test status: PASSING

Updated Tests:

- "should successfully submit a booking"
  * Updated to use day+5 start date (accounts for 3-day lead period)
  * Ensures test doesn't fail on past-date constraint

- Updated test comments and log messages to reflect server-side selection
  * Changed from "client-side selectOptimalItem()" references
  * Updated to "server-side optimal item selection"
  * Clarified that server performs the optimal selection

Test Coverage:
- End-to-end "any item" booking submission
- Smart window maximization algorithm
- Edit mode date initialization
- Server-side optimal selection validation
- Database verification of assigned items
- Modal interaction and form submission

Test plan:
1. Start KTD environment
2. Ensure Cypress is installed: yarn install
3. Run: npx cypress run --spec "t/cypress/integration/Circulation/bookingsModalBasic_spec.ts"
4. Verify all 11 tests pass
5. Optionally run in interactive mode: npx cypress open

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:04 -07:00
Martin Renvoize 39fadd016a
Bug 40134: Implement client-side improvements for bookings
This patch implements client-side improvements for the bookings modal,
including smart window maximization and hybrid submission logic that
delegates optimal item selection to the server.

Smart Window Maximization:
- Implements "never re-add items to pool" algorithm
- Dynamic item pool reduction based on availability
- isDateDisabledForItemtype() calculates available dates
- isDateInMaximumWindow() validates end dates against item pool
- Provides real-time calendar feedback to users

Hybrid Submission Logic:
For "any item" bookings, the client now uses a smart hybrid approach:
1. Filters items by selected itemtype
2. Checks availability for the booking period
3. If 0 items available: Shows error (no suitable item)
4. If 1 item available: Sends item_id (optimization - skips server)
5. If 2+ items available: Sends itemtype_id (delegates to server)

This minimizes server round-trips while delegating complex optimal
selection logic to the server when multiple choices exist.

Test plan:
1. Navigate to biblio detail page with bookable items
2. Click "Place booking" and select patron/pickup location
3. Select itemtype (not specific item) - "any item" mode
4. Select start date - verify end dates are maximized
5. Verify calendar disables dates when no items available
6. Submit booking - verify it succeeds
7. Check that optimal item was selected (server-side)
8. Test edit mode with "any item" selection
9. Verify both create and edit use same logic

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:03 -07:00
Martin Renvoize a6766a9e80
Bug 40134: Add unit tests for optimal item selection
This patch adds unit tests for the server-side optimal item selection
functionality.

t/db_dependent/Koha/Booking.t:
- Added subtest for _select_optimal_item() method (7 tests)
  * Tests with no items (returns undef)
  * Tests with single item (returns that item)
  * Tests with multiple items (selects item with longest future availability)
  * Tests optimal selection across items with different booking patterns
  * Tests handling of cancelled bookings (ignored in selection)
  * Tests iterator reset after selection
- Added subtest for _assign_item_for_booking() with itemtype filtering (6 tests)
  * Tests booking without item_id or itemtype_id (selects any available)
  * Tests booking with itemtype_id (filters by type)
  * Tests optimal selection within filtered itemtype
  * Tests exception when no items of specified type available
- Added integration test for full optimal selection workflow (5 tests)
  * Creates 3 items with different future booking patterns
  * Verifies items selected in optimal order (B→C→A)
  * Demonstrates preservation of constrained resources
  * Validates complete algorithm chain
  * Tests booking failure when all items exhausted

t/db_dependent/api/v1/bookings.t:
- Added subtest for itemtype_id parameter validation (16 tests)
  * Tests successful booking creation with itemtype_id
  * Tests that item is automatically assigned
  * Tests validation: cannot specify both item_id and itemtype_id
  * Tests validation: must specify either item_id or itemtype_id
  * Tests clash detection with itemtype-based bookings
- Fixed duplicate booking_id test to use different dates
  * Prevents clash detection from masking duplicate ID constraint
- Updated test count from 3 to 8 subtests in Booking.t
- Updated test count to match actual assertions (16 not 18)

Test Coverage:
- Internal method testing: _select_optimal_item()
- Integration testing: _assign_item_for_booking()
- API endpoint testing: add() with itemtype_id
- End-to-end workflow validation
- Edge cases: empty sets, single items, ties, cancelled bookings

Test plan:
1. prove t/db_dependent/Koha/Booking.t
2. prove t/db_dependent/api/v1/bookings.t
3. Verify all tests pass

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:51:03 -07:00
Martin Renvoize c7fa646929
Bug 40134: Implement optimal item selection for 'any item' bookings
This patch implements an algorithm for selecting the item that would
leave the largest gap after the proposed booking to maximize the
opportunity for future bookings.

Key Changes:

Koha::Booking:
- Added set_itemtype_filter() method for transient itemtype parameter
- Added _select_optimal_item() method using single aggregated query
  * Groups bookings by item_id with MIN(start_date)
  * Selects items with no future bookings (optimal)
  * Among items with bookings, selects one with latest next booking
  * O(1) complexity - single database query with GROUP BY
- Updated _assign_item_for_booking() to use optimal selection
  * Filters by itemtype when itemtype_id is provided
  * Replaces previous random selection (RAND())

API Changes:
- Added itemtype_id parameter support to add() endpoint
- Added itemtype_id parameter support to update() endpoint
- Validates exactly one of item_id or itemtype_id is provided
- itemtype_id is transient - not persisted to database
- Extracted and applied via set_itemtype_filter() before store()

Architecture:
- itemtype_id is NOT a database column (transient only)
- Used as selection criterion, then discarded

Test plan:
1. Apply patch and restart services
2. Create "any item" booking via staff interface
3. Verify item with longest future availability is selected
4. Test via API with itemtype_id parameter
5. Verify itemtype_id is not persisted to database
6. Run t/db_dependent/Koha/Booking.t
7. Run t/db_dependent/api/v1/bookings.t

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-10 08:50:56 -07:00
Jonathan Druart 458a163360
Bug 39745: Replace more occurrences in selenium tests
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 12:57:59 -07:00
Lucas Gass 23934c8edc
Bug 3492: (RM follow-up) Adjust tests
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 12:57:58 -07:00
Lucas Gass d06e3d87a4
Bug 3492: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 12:57:58 -07:00
Lucas Gass 35154d19e1
Bug 3492: DBRev 25.12.00.023
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 12:57:57 -07:00
Martin Renvoize 1c893cc019
Bug 3492: (follow-up) Remove last reservefee references
Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 12:57:57 -07:00
Martin Renvoize f1b20b645c
Bug 3492: Modernize hold fee system and migrate to circulation rules
This commit completely modernizes the hold fee system by migrating from
the deprecated categories.reservefee column to the circulation rules framework
and implementing modern Koha::Objects patterns.

WHAT CHANGED:

1. MIGRATION TO CIRCULATION RULES:
   - Moved hold fees from categories.reservefee to circulation_rules table
   - Added database migration script with atomic update
   - Maintains existing fee amounts during migration
   - Provides rule hierarchy: category+itemtype+branch specific rules

2. NEW SYSTEM PREFERENCE: TitleHoldFeeStrategy
   - Options: highest, lowest, most_common
   - Addresses inconsistent title-level hold fees when items have different fees
   - Default: 'highest' (safest approach for library revenue)

3. MODERN Koha::Objects API:
   - Koha::Item->holds_fee($patron): Calculate fee for specific item
   - Koha::Hold->calculate_hold_fee(): Enhanced item/title-level logic
   - Koha::Hold->should_charge($stage): Centralized charging logic
   - Koha::Hold->charge_hold_fee(): Modern fee charging with proper accounting
   - Removed C4::Reserves::GetReserveFee and ChargeReserveFee

4. ENHANCED FUNCTIONALITY:
   - Proper ReservesControlBranch logic (patron home vs item home library)
   - Complete HoldFeeMode implementation (placement/collection/not_always)
   - Clear OPAC messaging with "You will be charged..." commitment
   - Comprehensive test coverage for all fee scenarios

5. TECHNICAL IMPROVEMENTS:
   - Object-oriented design following Koha patterns
   - Proper error handling and edge case management
   - Modern test structure with comprehensive coverage
   - Clean separation between fee calculation and charging

TEST PLAN:

A) MIGRATION AND SETUP:
   1. Apply all patches and run database update
   2. Verify existing hold fees migrated to circulation rules:
      - Administration > Circulation and fine rules
      - Previous categories.reservefee values now in "Hold fee" column
   3. Confirm old categories.reservefee column no longer used

B) BASIC FEE CALCULATION:
   1. Set up patron categories with different hold fees in circulation rules
   2. Create items with different item types, each with different hold fees
   3. Test item-level holds charge correct fees based on specific item
   4. Test title-level holds use TitleHoldFeeStrategy preference

C) TITLE-LEVEL HOLD STRATEGIES:
   1. Set TitleHoldFeeStrategy to "highest":
      - Create title with items having fees $1.00, $2.00, $3.00
      - Verify title-level holds charge $3.00
   2. Set TitleHoldFeeStrategy to "lowest":
      - Verify same title charges $1.00
   3. Set TitleHoldFeeStrategy to "most_common":
      - Add multiple items with $2.00 fee
      - Verify title-level holds charge $2.00 (most common)

D) HOLDFEEMODE PREFERENCES:
   1. Set HoldFeeMode to "any_time_is_placed":
      - Place holds and verify immediate fee charging
      - Check patron account for correct hold fee debits
   2. Set HoldFeeMode to "any_time_is_collected":
      - Place holds, verify no immediate charges
      - Check out items to patron, verify fee charged on collection
   3. Set HoldFeeMode to "not_always":
      - With available items: verify no fee charged
      - With all items checked out but no other holds: verify no fee
      - With all items checked out and other holds exist: verify fee charged

E) RULE HIERARCHY TESTING:
   1. Set different fees for different category/itemtype/branch combinations
   2. Verify most specific rule applies:
      - Branch + Category + ItemType (most specific)
      - Category + ItemType
      - Category only (most general)
   3. Test with different patron categories and item types

F) USER INTERFACE TESTING:
   1. OPAC interface:
      - Verify "You will be charged..." messages display correct amounts
      - Test both item-level and title-level hold fee messages
      - Confirm fees match actual charges in patron account
   2. Staff interface:
      - Place holds for patrons and verify fee calculations
      - Check Administration > Circulation rules interface works correctly

G) EDGE CASES AND ERROR HANDLING:
   1. Test $0.00 fees (no charge scenarios)
   2. Test patrons with no applicable fee rules (should default to $0.00)
   3. Test items with no holdable copies (should return $0.00)
   4. Test invalid/missing itemtypes or categories
   5. Test holds on biblios with no items

H) REGRESSION TESTING:
   1. Verify existing hold placement workflows unchanged
   2. Check patron account charging remains accurate
   3. Confirm OPAC and staff interfaces display correctly
   4. Test that existing holds continue to work properly

I) UNIT TESTS:
   Run the following test suites to verify functionality:
   - prove t/db_dependent/Koha/Hold.t
   - prove t/db_dependent/Koha/Item.t
   - prove t/db_dependent/Reserves.t
   - prove t/db_dependent/Circulation.t

BACKWARDS COMPATIBILITY:
- All existing functionality preserved
- Hold fee amounts maintained during migration
- Existing workflows continue unchanged
- Legacy code updated to use new API seamlessly

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 12:57:56 -07:00
Martin Renvoize d5f9858307
Bug 3492: Remove from patron categories admin
Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 12:57:56 -07:00
Martin Renvoize 35c7f0a7c4
Bug 3492: Add hold fees to circulation rules system
This commit integrates hold fee configuration into the main circulation
rules system, providing granular control over hold fees based on patron
category, item type, and branch combinations.

Changes:
- Add hold_fee as a new circulation rule type with full scope support
- Remove deprecated reservefee column mapping from Koha::Patron::Category
- Integrate hold fee input into the circulation rules admin interface
- Add hold fee column to the main circulation rules matrix
- Enable proper rule hierarchy resolution for hold fees

Hold fees can now be configured at multiple levels with automatic
fallback handling following standard circulation rule patterns.

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 12:57:55 -07:00
Martin Renvoize f5bc5be08d
Bug 3492: Database migration for hold fees to circulation rules
This commit adds the database migration to move hold fee configuration
from patron categories to circulation rules, providing more granular
control over hold fees based on patron category, item type, and branch.

The migration:
- Creates circulation rules for existing categories.reservefee values
- Removes the deprecated reservefee column from categories table

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 12:57:48 -07:00
Lucas Gass f4ddcd98bc
Bug 41624: (RM follow-up) Fix line at EOF
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 08:40:23 -07:00
Lucas Gass 977a8be9eb
Bug 41624: DBRev 25.12.00.022
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 08:40:22 -07:00
Jonathan Druart 58baeec11f
Bug 41624: Simplify db rev
Also fix QA failures:
[FAIL] installer/data/mysql/atomicupdate/bug_41624.pl
   FAIL   file_permissions
                File must have the exec flag
   FAIL   forbidden_patterns
                forbidden pattern: trailing space char (line 13)

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 08:40:22 -07:00
Lucas Gass 05d08e0ea4
Bug 41624: Revert bug 35211
Bug 41624: Revert "Bug 35211: (QA follow-up) Fix counting"

This reverts commit 97a022054d.

Bug 41624: Revert "Bug 35211: (QA follow-up) fix display of other results tab and move template toolkit logic outside of script array"

This reverts commit c4c2a4b235.

Bug 41624: Revert "Bug 35211: Removed local items from group tabs and removed group items from other holdings tab"

This reverts commit 124c6c8670.

Bug 41624: Revert "Bug 35211: Fixed group tab item building logic"

This reverts commit 23eb2dbd39.

Bug 41624: Revert "Bug 35211: Fix tab building"

This reverts commit f438535b5c.

Bug 41624: Revert "Bug 35211: Set SeparateHoldingsByGroup to separate based on SeparateHoldingsBranch syspref"

This reverts commit d2a0527d86.

Bug 41624: Revert "Bug 35211: Fixed count on logged in holdings"

This reverts commit 96c7eb3459.

Bug 41624: Revert "Bug 35211: Reordered tabs in Holdings Table and fixed group tabnames"

This reverts commit 5c2b0fd496.

Bug 41624: Revert "Bug 35211: Separate group holdings into individual tabs"

This reverts commit 08045c8009.

Bug 41624: DB update to remove the pref

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 08:40:21 -07:00
Jonathan Druart 080f5a2497
Bug 41747: Tidy js/ill-availability.js
xt/js_tidy.t does not check all files every run (it's using the incremental runs).
Something happened (node version changed?) and this is triggered because bug 41512 modified those files

Test plan:
0. Do not apply this patch
1. KOHA_CI_INCREMENTAL_RUNS=1 prove xt/js_tidy.t
=> fail
2. Apply this patch
3. Run prove again
=> pass

4. Run prove without the env var set
=> pass (i.e. there are no other JS files not tidy)

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-09 08:40:14 -07:00
Andrew Fuerste Henry c58989fc51
Bug 41588: Normalize spaces in 856$u links
To test:
1 - add 856$u " www.cheese.com" to a record
2 - confirm link does not work from results or details in either staff interface or OPAC
3 - add 856$u "www.cheese.com " to a record
4 - confirm link does not work from results or details in either staff interface or OPAC
7 - apply patch, restart_all
8 - reload pages, confirm both links work in results and details in staff interface and OPAC

Optional final step: have some cheese as a treat

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:41 -07:00
Lucas Gass b626e4f3bf
Bug 41701: DBRev 25.12.00.021
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:41 -07:00
Owen Leonard eb69c7a735
Bug 41701: Fix definition of OAI-PMH:DeletedRecord preference in sysprefs.sql
This patch updates the definition of the OAI-PMH:DeletedRecord
preference in sysprefs.sql, correcting the switched options and
explanation columns.

To test, apply the patch and run updatedatabase. Check that the database
has been updated correctly:

SELECT `options`, `explanation` FROM `systempreferences` WHERE
`variable` = 'OAI-PMH:DeletedRecord';

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:40 -07:00
Nick Clemens f55a6c8851
Bug 41093: Specify blank value for 'None specified'
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:40 -07:00
Lucas Gass 6d005fbc0c
Bug 39802: DBRev 25.12.00.020
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:39 -07:00
Martin Renvoize 32d277687d
Bug 39802: (QA follow-up) Typo fixes
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:39 -07:00
Nick Clemens 1249cea96d
Bug 39802: Add LostChargesControl system preference
Sponsored-by: MAIN Library Alliance
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:38 -07:00
Nick Clemens 6cf5b8d509
Bug 39802: Atomic update file
Sponsored-by: MAIN Library Alliance
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:38 -07:00
Nick Clemens f5377250f5
Bug 39802: Report when no branches will be selected and exit
This highlights the issue mentioned in the last patch, reporting that
nothign will be done and exiting the script

To test:
1 - perl misc/cronjobs/longoverdue.pl  -l 5=1 --library CPL
2 - Confirm script exits with warning that nothing has been done

Sponsored-by: MAIN Library Alliance
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:37 -07:00
Nick Clemens 95789607e2
Bug 39802: Use new preference in longoverdue cron
The test plan here is largely confirming that nothing has changed.

Using CircControl here has an existing problem - when set to 'PickupLibrary'
the check in this cron looks for items from the userenv branch - the cron doesn't
have a userenv branch, it is undef, so for any library limiting by branch and
with CircControl set to 'PickupLibrary' / 'the library you are logged in at' the cron will do nothing

Sponsored-by: MAIN Library Alliance
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:37 -07:00
Martin Renvoize aee1c8f7a5
Bug 40095: Add context to intranet_js plugin hook
The get_plugins_intranet_js method now passes the current script name
(controller) to plugins via a 'page' parameter, allowing plugins to
determine which page/controller is being displayed.

This will allow plugin authors to only return JS that's relevant to the
current page when they wish to do so.

Test plan:
1. Create a plugin with an intranet_js method that logs the received parameters
2. Navigate to different pages in the staff interface
3. Verify that the plugin receives the page parameter containing the script name
4. Confirm that the script name reflects the actual controller (e.g.,
   '/cgi-bin/koha/admin/admin-home.pl' when on the admin home page)
5. Run the test: prove t/db_dependent/Koha/Template/Plugin/KohaPlugins.t
6. Verify that the new test passes, confirming plugins receive the page parameter

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:36 -07:00
Kyle M Hall c2b7f2ddb2
Bug 37402: (follow-up) Zero pad the hour automatically on blur
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:36 -07:00
Owen Leonard 5a8a8ffd57
Bug 37402: (follow-up) Add standard 'required' attributes and labels
This patch also moves time format hint into a standard <div
class="hint">. The hour format has been corrected from hhmm to HH:MM.

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:35 -07:00
adam.styles 38c3c56045
Bug 37402: Applying scheduler form UI changes to ensure correct form state fail
Also adding form place holder and label update to better inform users

1. login to koha instance
2. navigate to reports module
3. create a small quick test report or use an existing small test report
4. select 'schedule' from toolbar above main sql window
5. once redirected to task scheduler UI, choose 'tommorow' in drop down
   'date' field.
6. enter 'test@local.com' in 'email' field
7. enter no value in the 'time' field
8. press 'save'.

Result - PASS:
- No  'Failed to add scheduled task' error state occurs, and the correct
  time require error state does occur
- Form does not attempt submit as it identifies the required time field
  data is missing
- Addition time field place holder information is provided and time
  field label also offer time format information to user

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:35 -07:00
Baptiste Wojtkowski e1ef80682a
Bug 41420: Fix referrer in parcel.tt
Test plan:

Steps to reproduce :
1 - Go to http://localhost:8081/cgi-bin/koha/acqui/neworderempty.pl?booksellerid=1&basketno=1 and fill in a new item.
2 - Visit http://localhost:8081/cgi-bin/koha/acqui/parcels.pl?booksellerid=1, type an invoice name and validate. Inspect both "Cancel order" and "Cancel order and catalog record". Notice the link in "Cancel order is broken"
3 - Apply patch
4 - Repeat 3 and notice the referrer is well formed.

For a reason I ignore, it does not seem to have an impact on main, but
on the 24.11 I tested, if you click on cancel order, after confirming
the deletion and clicking on "ok", you will be asked again to confirm,
    however the deletion has been performed

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:34 -07:00
Lucas Gass 61107cbfd4
Bug 36466: DBRev 25.12.00.019
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:34 -07:00
Hammat Wele 2e3325a680
Bug 36466: (follow-up) Use NULL for blank planned and published dates instead of today
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:33 -07:00
Hammat Wele 085c27e41b
Bug 36466: (follow-up) Adding atomicupdate file to fix 0000-00-00 in serial table
WNC amended patch - tidied

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:33 -07:00
Hammat Wele cbdd895dbf
Bug 36466: Incorrect date value stored when 'Published on' or 'Expected on' are empty
When we edit a serial and we leave the date fields ('Published on' or 'Expected on') empty incorrect date are saved in the database 0000-00-00
instade of current date and we get Error 500

to reproduce
1. Connect to staff interface
2. Go to Serials and create a new subscription
3. Click on Serial collection in the left menu (and note the
   Subscription numnber)
4. Select a serial to edit and Click on «Edit serials»
5. In the Serial edition form, delete «Published on > and « Expected on
   » dates and save
    ----> Error 500 page is displayed
6. Check the value of «Published on > and « Expected on » dates  in the
   database
   select planneddate,publisheddate from serial where subscriptionid=<Subscription numnber in step 3>;
    ------> planneddate publisheddate have «0000-00-00» value
7. Apply the patch
8. run ./installer/data/mysql/updatedatabase.pl
9. repeat step 2, 3, 4, 5, 6
   ----->  Error 500 page is no more displayed
   -----> «Published on » and « Expected on » field have NULL as value

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:32 -07:00
Lucas Gass b190813020
Bug 41368: Fix View batch click
To test:
1. APPLY PATCH
2. yarn build
3. cypress run --spec t/cypress/integration/Tools/ManageMarcImport_spec.ts
4. It should now pass

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:32 -07:00
Owen Leonard 5b8756f7b2
Bug 41764: ISSN hidden input missing from Z39.50 search form navigation
This patch modifies the Acquisitions and Cataloging Z39.50 search forms
so that the pagination form includes a hidden ISSN input. This is
necessary for the form to submit the same search parameters each time
the user requests a new page of results.

To test, apply the patch and go to Acquisitions.

- Search for a vendor, and create a basket if necessary.
- Add to the basket and choose "From an external source."
- In the "ISSN" field, enter a partial ISSN number, e.g. 0361, and
  search.
- At the bottom of the search results, click the "Next page" button.
- The second page of results should load correctly. At the top of the
  table of results it should say "You searched for: ISSN: 0361"
- Perform the same test in Cataloging:
  - Cataloging -> New from Z39.50/SRU

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:31 -07:00
Baptiste c800ea71de
Bug 40960: Print notification only if selected type is impossible
Test plan (ktd):
1 - Have henry acevedo have no mail and set messaging preference "Hold filled to sms"
2 - Have edna acosta have not messaging preference
3 - Place an hold on two books for edna acosta and henry acevedo, check in both books and select the button to fill the hold
4 - Check in notices and see that both received a notice
5 - Apply tests and run tests -> they should fail
6 - Apply patch, run rest -> they should pass
7 - Unfill both holds and fill them back. Henry should keep receiving notices while Edna should not anymore.

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:31 -07:00
Baptiste ed42880f3d
Bug 40960: Fix unit tests
Two unit tests have been moved/replaced to fit the desired behaviour

- "patron notified when item set to waiting" -> does not fit the desired behaviour.
  The desired behaviour for mails/sms is already tested
  The desired behaviour for prints is changed to "patron not notified if
  no messaging preference is specified"
- "patron not notified a second time (bug 11445)" -> Moved into
  _koha_notify_reserve tests

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:30 -07:00
Lucas Gass d2a3fe93f2
Bug 41475: Make hostitemnumbers an array ref
To test:

1. Set 'EasyAnalyticalRecords' to 'Show'.
2. Find or create a parent record that has at least 2 items on it. Note the itemnumbers and the framework.
3. Edit that framework so you can add a 773$0, 773$9, and 773$t
4. Find or create a child record.
5. Make at least 2 773 entries on the child record. Add the biblionumber of the host record to the 773$0.
6. In each entry for the 773$9 add one of the itemnumbers from the host record.
7. For the 773$t and the biblio title of the host record.
8. Save.
9. Now attempt to put a hold on the child record.
10. 500 error:
11. APPLY PATCH and restart_all
12. Try placing the hold again, this time it succeeds.

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:30 -07:00
Emmi Takkinen c27f4e54ac
Bug 41431: Use correct data-attribute to fetch existing circulation rule note
If you click Edit on a line with a note, the note field is not displayed
in the editing line and is blanked upon save (i.e., the note is lost,
even if you made no edits on the note). Value of the note is stored to
data-attribute which was changed to use Bootstraps own data-attribute.
So now the value must be fetched with keyword "bsContent" instead of
just "content".

To test:
1. Add note for a circulation rule.
2. Edit the rule.
=> Note that notes value is not displayed in editing row.
3. Apply this patch.
4. Edit rule again.
=> Note that notes value is now displayed as expected.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:29 -07:00
Marcel de Rooy 56410f9c90
Bug 19690: Change comments in IsAvailableForItemLevelRequest
The term 'If any unavailable' is not very clear but it is also
very hard to find something short and better.

A next available hold is possible when at least one item is
unavailable (checked out, on order, waiting/transit hold).
A item level hold is possible when the specific item is
unavailable.

Test plan:
Read the patch. The code did not change.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:29 -07:00
Tomás Cohen Arazi b7ab84f560
Bug 41617: Remove extra spaces breaking CSV formatting
This patch removes extra spaces breaking CSV parsing in tools like Excel.

To test:
1. Perform an item search selecting the CSV option
2. Open the CSV with a TEXT EDITOR of your choice
=> FAIL: Notice the extra spaces after the commas (those used as separators, no the ones within the double quotes)
3. Apply this patch
4. Repeat 1-2
=> SUCCESS: No extra spaces!
5. Sign off :-D

Note: On the first attempt you can open the file on Excel if you have it and notice things are not aligned.
Note2: Using double quotes on all fields regardless of the context works for most parsers, but is not correct CSV. Will deal with it on a separate bug.

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: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:28 -07:00
Martin Renvoize 971e2ead4b
Bug 41749: Display patron consents on staff patron detail page
Adds a read-only "Consents" section to the staff patron detail page
showing the patron's consent status for GDPR (when PrivacyPolicyConsent
is enabled) and any plugin-defined consent types.

Each consent displays with visual status indicators:
- Green checkmark with timestamp when consent was given
- Red X with timestamp when consent was refused
- Gray "Not specified" when no consent recorded

Test plan:
1. Enable PrivacyPolicyConsent system preference
2. As patron in OPAC: give/refuse consent at "Your consents"
3. As staff: view patron detail page
4. Verify "Consents" section shows correct status and timestamp
5. Test with no consent recorded (should show "Not specified")
6. If available, test with plugin-defined consent types

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-02-06 15:17:28 -07:00
Andrew Fuerste Henry cc68988bce
Bug 41752: label concactfirstname and contactname as Guarantor first name and Guarantor surname
This change impacts the display of patron fields listed in system preferences:
BorrowerMandatoryField
BorrowerUnwantedField
PatronSelfModificationBorrowerUnwantedField
PatronSelfRegistrationBorrowerMandatoryField
PatronSelfRegistrationBorrowerUnwantedField

To test:
1 - confirm that the sysprefs listed above include "Alternate contact: First name (contactfirstname)" and "Alternate contact: Surname (contactname)"
2 - confirm that toggling the display of these fields hides/shows the Guarantor first name and Guarantor surname fields in a patron record
3 - apply patch, restart_all
4 - confirm that sysprefs now show "Guarantor first name (contactfirstname)" and "Guarnator surname (contactname)"
5 - confirm toggling the display of these fields still hides/shows the correct fields in a patron record in the staff client, in self-ref, and in OPAC self-modification

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:27 -07:00
Ayoub Glizi-Vicioso 707a208e16
Bug 41689: Make patron message labels translatable
To Test:

BEFORE APPLYING PATCH:
1- Go to any patron account > Add message
2- Add a message for: "Staff - Internal note"
3- Add a second message for: "OPAC - [NAME OF THE PATRON SELECTED]"

4- Add any language (I'll use french-Canada, fr-CA, in this case):
From your koha instance, enter on the command line:
./misc/translator/translate install fr-CA

5- Go to Administration > System preferences > enter language
6- Make sure to check 'Français (fr-CA)' box from the 'StaffInterfaceLanguages'preference and save.

7- Go back to the specific patron in which you added the messages
8- Switch language to 'Français' (bottom left corner)

9- Notice that:
--> 'OPAC' is still the same (Since we spell OPAC the same way in both english and french. However, it might not be the case in other languages).
--> 'Staff note' is not translated.

10- apply patch

AFTER APPLYING PATCH:

11 - From you koha instance, enter on the command line:

./misc/translator/translate update fr-CA

./misc/translator/translate install fr-CA

12- Reload patron's page

13- Notice that 'Staff note' is now translated
** You should see this output: 'Note interne'

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:27 -07:00
Owen Leonard 531dbe668b
Bug 37773: Show search term in cataloging search results
This patch updates the cataloging search results page so that the search
term is shown in the page title, breadcrumb, and headings.

To test, apply the patch and go to Cataloging in the staff interface.

- Perform searches which will return varying results:
  - A search which returns records in both the catalog and the reservoir
    (perform some Z39.50 searches if the reservoir is empty).
  - A search which returns records in the catalog but not the reservoir.
  - A search which returns records in the reservoir but not the catalog.
  - A search which returns no results at all.
- In all cases, check the page title, breadcrumbs, and headings. Your
  search term should appear as expected.
- Test that the correct strings are translatable. In this example I'm
  testing fr-FR:

    - Update a translation:

    > gulp po:update
    > cd misc/translator
    > perl translate update fr-FR

    - Open the corresponding .po file for the strings pulled from the
      template e.g.  misc/translator/po/fr-FR-messages.po
    - Locate strings pulled from addbooks.tt for translation, e.g.:

      #: koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbooks.tt:93
      #: koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbooks.tt:207
      #, fuzzy, perl-brace-format
      msgid "No catalog search results for '{query}'"
      msgstr "Résultats de recherche pour '{query}'"

    - Delete the line starting with "#, fuzzy"
    - Edit the "msgstr" string however you want (it's just for testing)
    - Install the updated translation:

    > perl translate install fr-FR

- Switch to your updated translation in the staff interface and perform
  the same tests as above. The translations should appear as expected.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:26 -07:00
Owen Leonard e948934081
Bug 39255: Improve translation of title tags: OPAC part 4
This patch updates a batch of OPAC templates so that title tags can
be more easily translated.

To test, apply the patch and confirm that the following pages have
correct title tags:

- The summary page for a logged in user
- Most popular
- Purchase suggestions
- Recent comments
- Tags
- Lists
  - List of lists
  - List contents
  - Send list
  - Share a private list
- Send a cart
- View a subscription -> More details
- The opac-showmarc page isn't usually accessed directly but you can
  test it by opening the URL, e.g.
  http://localhost:8080/cgi-bin/koha/opac-showmarc.pl?viewas=html&id=5
- Self checkin (with SelfCheckInModule enabled).
- Self checkout (with WebBasedSelfCheck enabled).
  - Self checkout help
  - Self checkout slip

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:26 -07:00
Lucas Gass 3e1b77f4f3
Bug 41536: (follow-up) Tidy the template
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:25 -07:00
Pedro Amorim 9c8a475d08
Bug 41536: Move 'Edit request' button to after backend actions buttons
This ensures that the 'Confirm request' will always be the first button in the toolbar.

Test plan:
1) Apply patch (skip this if sandbox)
2) Enable the ILLModule sys pref
3) Create a new request, visit:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
4) Add any type, '42' as cardnumber, any library. Click 'Create'.
5) Notice the 'Confirm request' button is shown first, and with the 'primary button' styling.

Signed-off-by: Photonyx <Headtummy@gmail.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:25 -07:00
Pedro Amorim c47bbdec8d
Bug 41536: Add 'btn-primary' class to 'Confirm request' button
This makes the button stand out from the other available actions

Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:24 -07:00
Martin Renvoize 59b4f329a4
Bug 7376: (QA follow-up) Trivial typo fixes
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:24 -07:00
Baptiste Wojtkowski bdf67484bf
Bug 7376: (follow-up) Add tests on no_return
Note: tests have to be performed on addReturn and have therefore been
added in a separate test

Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:23 -07:00
Martin Renvoize cfb6643309
Bug 7376: Allow passing 'transferbranch' to CanBookBeReturned
This patch adds the optional 'tranferbranch' parameter to the
CanBookBeReturned routine. This allows for passing the correct
'ReturnToX' transfer to be tested at return time for transfer limit
restrictions.

Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Mathieu Saby <mathsabypro@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:23 -07:00
Baptiste Wojtkowski bf01111fdb
Bug 7376: Clarify tests further
We want to prevent returns where the item cannot be transfered 'back to
where it came from (be that Homebranch or Holdingbranch as defined
respectively in the circulation rules).

This patch clarifies the tests further by testing this transfer
functionality distinctly from the 'AllowReturnToBranch' preference
functionality.

Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Mathieu Saby <mathsabypro@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:22 -07:00
Arthur Suzuki b13b26ecda
Bug 7376: (follow-up) Fix C4/Circulation.pm
This follow-up patch actually fixes the issue.
This patch forbids a patron to return a book to a library
from where the book may not return to its home or holding library.

Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Mathieu Saby <mathsabypro@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:22 -07:00
Baptiste Wojtkowski 023a2cc79f
Bug 7376: (follow-up) Make extended tests mock the actual desired behaviour
Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Mathieu Saby <mathsabypro@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:21 -07:00
Martin Renvoize 7b634c02fb
Bug 7376: (follow-up) Extend test coverage and document
I'm not sure about the requirements for this bug. This patch extends the
tests to cover the new combinations introduced and highlights exactly
what changes in the logic.. which appears to be fairly little.

Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Mathieu Saby <mathsabypro@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:21 -07:00
Arthur Suzuki 5b5bc64e70
Bug 7376: Unit test
Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Mathieu Saby <mathsabypro@gmail.com>
Rebased-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:20 -07:00
Lari Taskula c98d1c08a0
Bug 7376: Transfer limits should be checked at check-in
Test case:
 * UseBranchTransferLimits must be set
 * define your branch transfer limit. Refuse transfers from libraryA to libraryB
 * checkout a book owned by libraryB, from libraryB, with a librarian located at libraryB
 * move the librarian to libraryA ("Set Library" link top/right)
 * check-in the book => it's possible whatever your setup

After the patch, the behaviour respect the branch transfer limit parameter:
you can check-in if you accept transfers, you can't if you refuse them.

(Note: IndependantBranches must be OFF, otherwise it's not possible to do the checkin whatever the branch transfer limits)

TEST PLAN:
Note: older signoffs did not validate this test plan

Considering you use a ktd-like database.
Log in at centerville
Allow transfers between centerville, Midway and Franklin, don't allow transfers between Fairfield, Liberty and all other libraries. In addition, allow the transfer between Liberty and Midway

Transfer as much books as you can from Centerville to Midway.
Log in as Midway, check out as many books from Centerville as you can to Henry Acevedo. If you don't have enough books during the test plan, feel free to check out books again. During the whole test, without further precision, items checkout out in such conditions should be checked in.
You will also need 6 checked out floating books at midway

Note:
'-> ok' means the checkin is allowed
'-> not ok' means the checkin is not allowed

Apply patch
I - Test AllowReturnToBranch
    During this part of the test plan, you want to have all following configuration :
    'CircControlReturnsBranch' -> 'The library the item is checked in at'
    In CIRC rules: 'Default holds policy and return policy' -> 'Item returns home' (make sure you have no other holds policy on any library)
    'AllowReturnToBranch' -> as follows
    1 - Set to "Either at the library the item is from or at the library the item was checked out from"
X        a - Check in to Centerville -> ok
X        b - Check in to Midway -> ok
X        c - Check in to Franklin -> not ok
X        d - Check in to Fairfield -> not ok
    2 - Set to "Only at the library the item was checked out from"
X        a - Check in to Centerville -> not ok
X        b - Check in to Midway -> ok
X        c - Check in to Franklin -> not ok
X        d - Check in to Fairfield -> not ok
    3 - Set to "Only at the library the item is from"
X        a - Check in to Centerville -> ok
X        b - Check in to Midway -> not ok
X        c - Check in to Franklin -> not ok
X        d - Check in to Fairfield -> not ok
    4 - Set to "Any library"
X        a - Check in to Centerville -> ok
X        b - Check in to Midway -> ok
X        c - Check in to Franklin -> ok
X        d - Check in to Fairfield -> not ok

II - Transfer and check out 6 floating items at midway.
    1 -
X        a - Check in to Centerville -> ok
X        b - Check in to Midway -> ok
X        c - Check in to Franklin -> ok
X        d - Check in to Fairfield -> ok
    2 - Set 'AllowReturnToBranch' to 'home library'
X        a - Check in to Centerville -> ok
X        b - Check in to Midway -> not ok

III - Pathological & asymetrical cases.
    During this part of the test plan, you want to have all following configuration :
    'CircControlReturnsBranch' -> 'The library the item is checked in at'
    'Default holds policy and return policy' -> 'Item returns home' (make sure you have no other holds policy on any library)
    'AllowReturnToBranch' -> 'Any library'
    FOR THIS PART OF THE TESTS ONLY (until IV): Don't allow tranfers from midway to centerville, but allow from centerville to midway
X        1 - Check in a book at midway -> not ok
    Set 'Default holds policy and return policy' -> 'Item returns to issuing library'
X        2 - Check in a book at centerville -> ok
    FOR THIS PART OF THE TESTS ONLY (until IV): Don't allow tranfers from centerville to midway
X        3 - Check in a book at centerville -> not ok
X        4 - Set 'AllowReturnToBranch' to "Only at the library the item was checked out from"
X            a - check in to Centerville -> not ok
X            b - check in to Midway -> not ok

IV - Test 'CircControlReturnsBranch'
    (remove the precedent transfer restriction)
    During this part of the test plan, you want to have all following configuration :
    'Default holds policy and return policy' -> 'Item returns home' (unchanged)
    'Default checkout, hold and return policy for Midway' -> 'Item returns to issuing library'
    'Default checkout, hold and return policy for Franklin' -> 'Item floats'
    'Default checkout, hold and return policy for Fairfield' -> 'Item returns to issuing library'
    'Default checkout, hold and return policy for Liberty' -> 'Item returns home'
    'AllowReturnToBranch' -> "Any library" (unchanged)
    'CircControlReturnsBranch' -> as follows
    1 - 'The library the item is checked in at'
X        a - Check in to Centerville -> ok
X        b - Check in to Midway -> ok
X        c - Check in to Franklin -> ok
X        d - Check in to Fairfield -> not ok
X        e - Check in to Liberty -> not ok
    2 - 'The library the current item is held by
X        a - Check in to Centerville -> ok
X        b - Check in to Midway -> ok
X        c - Check in to Franklin -> ok
X        d - Check in to Fairfield -> not ok
X        e - Check in to Liberty -> ok
    3 - 'the library the item is owned by'
X        a - Check in to Centerville -> ok
X        b - Check in to Midway -> ok
X        c - Check in to Franklin -> ok
X        d - Check in to Fairfield -> not ok
X        e - Check in to Liberty -> not ok

Sponsored-by: National Library of Finland
Signed-off-by: Arthur Suzuki <arthur.suzuki@biblibre.com>
Signed-off-by: Julian Maurice <julian.maurice@biblibre.com>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Mathieu Saby <mathsabypro@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:20 -07:00
David Cook cb040b07f0
Bug 34879: Remove unused file from Koha
This change removes the "catalogue/getitem-ajax.pl"
which appears to be an unused file in Koha.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:19 -07:00
Lucas Gass 27f4bb4c9d
Bug 41422: Fix I18N issue with FilterSearchResultsByLoggedInBranch
To test:
1. Apply patch
2. Search fof the FilterSearchResultsByLoggedInBranch system preference and set it to 'Do'.
3. Do a catalog search and notice the 'Show local items only' button above the "Location" column.
4. Click it to show information ( in that column ) that is only related to the branch you are currently logged in at.
5. In the same result set have some items that are checked out, withdrawn, lost, damaged, and notforlaon.
6. Play more with the search results and ensure everything is accurate.
7. Set the FilterSearchResultsByLoggedInBranch system preference to 'Don't' and confirm that there is no button to filter by branch anymore.

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:19 -07:00
Martin Renvoize 1f51dab88e
Bug 41268: (QA follow-up) Apply same simplification to remaining operations
This follow-up applies the same variable-based simplification pattern
to the remaining operations in smart-rules.pl:

- cud-set-branch-defaults: 2 code paths reduced to 1
- cud-add-branch-cat: 4 code paths reduced to 1
- cud-add-open-article-requests-limit: 4 code paths reduced to 1
- cud-del-open-article-requests-limit: 4 code paths reduced to 1
- cud-add-branch-item: 4 code paths reduced to 1

Note: cud-delete-branch-cat was NOT simplified because it contains
additional business logic (setting itemtype rules only when deleting
the default categorycode), which is not simply a matter of converting
'*' to undef.

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:18 -07:00
Nick Clemens e9d675fcbc
Bug 41268: Use variables rather than conditionals
Testing plan:

- Apply patch
- Run tests
- Go to admin > circ and fine rules
- Create a few Holds and bookings policies
  - For All Libraries
  - For a specific library
- Delete the policies
- (optional) a simple `SELECT * FROM circulation_rules` can confirm policies were created/deleted correctly

Signed-off-by: Eric Phetteplace <ephetteplace@cca.edu>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:18 -07:00
Tomás Cohen Arazi 402a251392
Bug 40966: Make sure 'whole_record' and 'weighted_fields' are passed around
The ES query builder correctly handles this parameters (passed from
search.pl and opac-search.pl) when it builds the ES query. But the
returned 'query_cgi' that is used later for keeping the search results
query context for building things like facets, just doesn't pass this
values around, leading to a loss of them on the generated links.

This patch makes sure they are passed around.

To test:

1. Apply the regression tests
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/SearchEngine/Elasticsearch/QueryBuilder.t
=> FAIL: Tests fail! These params are not passed around!
3. Set ElasticsearchMARCFormat="Searchable array"
4. Reindex all the things:
  k$ koha-elasticsearch --rebuild -d -v kohadev
5. Go to advanced search, using the 'More options' option.
6. Check 'Search entire MARC record' and 'Apply weights to search'
7. Perform a search on 'FULL TEXT'
=> SUCCESS: The URL contains `whole_record=on` and `weighted_fields=on`
8. Hover on any facet and look at the link
=> FAIL: It doesn't contain them!
9. Apply this patch
10. Restart all
  k$ restart_all
11. Refresh the search results
12. Repeat 6
=> SUCCESS: Links have the needed parameters!
13. Repeat 2
=> SUCCESS: Tests pass! the parameters are passed around!
14. Sign off :-D

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-02-06 15:17:17 -07:00
Tomás Cohen Arazi a1fa2dc71e
Bug 40966: Regression tests
Tests verify that whole_record and weighted_fields parameters are
correctly included in the query_cgi string returned by build_query_compat(),
ensuring these parameters are preserved in facet links and pagination.

To test:
1. Apply this patch
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/SearchEngine/Elasticsearch/QueryBuilder.t
=> FAIL: The values are not being passed around

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-02-06 15:17:17 -07:00
Tomás Cohen Arazi 3dd5eda7d5
Bug 41684: Make `notices_content` hook be used with `get_enabled_plugins()`
This patch makes the plugin hook load plugins using `get_enabled_plugins()`. This has
the immediate effect of using the already cached plugin objects without reinstantiating them.

This way transactions don't get broken for schema reloads. Also, only enabled plugins
are loaded/attempted to be loaded.

To test:
1. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Plugins/Notices_content_hook.t
=> SUCCESS: Tests pass
2. Apply this patch
3. Repeat 1
=> SUCCESS: Tests still pass: fundamental behavior remains unchanged.
4. Sign off :-D

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-02-06 15:17:16 -07:00
Jonathan Druart 1776ba0e4e
Bug 41457: Add missing html filter
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:16 -07:00
Jonathan Druart ba5aa257b6
Bug 41457: Fix column visibility on the holds history table
We must use bKohaColumnsUseNames to correctly deal with the column
visibility if some columns are shown/hidden depending on sysprefs.

On the holds history table we have "item type" that is hidden if
AllowHoldItemTypeSelection is disabled.

Test plan:
1. Create a hold and select an item type
Note: I think there is a bug if you select it from "Hold next available
item", use "Hold details". Please confirm?
2. Go to the holds history view
3. Hide some columns
4. Modify the value of AllowHoldItemTypeSelection
5. Reload the page and notice that the state of the table is restored
   correctly, however the "item type" column is shown or hidden
   depending on the syspref's value.

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:15 -07:00
Pedro Amorim afaee3651b
Bug 41204: Fallback to Standard earlier
The fallback to 'Standard' if 'FreeForm' needs to happen earlier in the code, before other backend checks are performed

Test plan, before applying patch:
1) Enabled ILLModule
2) Login to OPAC and access a FreeForm URL directly:
<opac_url>/cgi-bin/koha/opac-illrequests.pl?backend=FreeForm&op=add_form
3) Notice you get a 404. Apply patch and restart plack.
4) Repeat 2) Notice you get the 'Standard' form as expected. Create the ILL request. It's created as a 'Standard' request.

Signed-off-by: Leo O’Neill <leo.o’neill@uwl.ac.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:15 -07:00
Marcel de Rooy 8ba4d26dab
Bug 41449: Reserves.t - fix description and assumption for onshelfholds
Test plan:
prove t/db_dependent/Reserves.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:14 -07:00
Hammat Wele 1838b1c4a0
Bug 41655: Local OPAC covers are not displayed in OPAC lists
Local cover images are no longer displayed in OPAC lists since Bug 41078.
In Koha versions prior to 25.05.05, local cover images were correctly displayed in OPAC lists.

This appears to be a regression.

To reproduce

1. Set the system preferences LocalCoverImages and OPACLocalCoverImages to Show.

2. Find a bibliographic record and open the record detail page.

3. Upload a local cover image file for the record.

4. Create a public list and add the record to the list.

5. In the OPAC, open the public list.
   --> The local cover image is missing.

6. Apply the patch.

7. Reload the OPAC list page.
   --> The local cover image is now displayed correctly.

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:14 -07:00
Tomás Cohen Arazi 5060bde417
Bug 41340: Better translatability on 'batch_item_record_modification.inc'
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:13 -07:00
Jonathan Druart d4ee7bd5f0
Bug 40946: Remove warning "Aborted connection" from Koha/Z3950Responder/ZebraSession.t
start_server from Net::Z3950::SimpleServer is going to fork and the DB
server is aborting the connection

db-1         | 2025-10-03 12:02:12 272 [Warning] Aborted connection 272 to db: 'koha_kohadev' user: 'koha_kohadev' host: '172.19.0.6' (Got an error reading communication packets)

Test plan:
  prove t/db_dependent/Koha/Z3950Responder/ZebraSession.t
=> Without this patch you will see a warning in the db container log
=> With this patch apply the warning no longer appears

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:13 -07:00
Jonathan Druart ebfbd0da26
Bug 40947: Remove warning "Aborted connection" from t/db_dependent/www/search_utf8.t
We sent a SIGKILL to the daemon which does not let it cleanup

Updating to SIGTERM (15) and handle the dbh disconnect gracefully in the
daemon.

Also handle SIGINT (ctrl+c) only because I think it can be useful (?)

Test plan:
  prove t/db_dependent/www/search_utf8.t

=> Without this patch there are 3 warnings in the DB container log
db-1         | 2025-10-03 12:30:57 362 [Warning] Aborted connection 42 to db: 'koha_kohadev' user: 'koha_kohadev' host: '172.19.0.6' (Got an error reading communication packets)
=> With this patch applied there are none.

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:12 -07:00
Fridolin Somers 15f21656fe
Bug 41043: Use op 'add_form' and 'edit_form' in z3950 servers
Since CSRF protection, 'op' arg for creating/editing an object uses add_form and edit_form.
This is missing only in admin/z3950servers.pl

This patch also removes TT variable add_form to avoid confusion

Test plan :
1) Create a new z3950 server
2) Enter mandatory datas in form and save
3) Edit this server
4) Copy this server
5) Repeat 1 to 4 for a SRU server

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:12 -07:00
Fridolin Somers d0cdf16a45
Bug 41043: Use op 'add_form' and 'edit_form' in oai servers
Since CSRF protection, 'op' arg for creating/editing an object uses add_form and edit_form.
This is missing only in admin/oai_servers.pl

This patch also removes TT variable add_form to avoid confusion

Test plan :
1) Create a new OAI repository
2) Enter mandatory datas in form and save
3) Edit this repository
4) Copy this repository

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

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:11 -07:00
Pedro Amorim 9c9ba87468
Bug 36360: Add tests
prove t/db_dependent/Koha/Patrons.t

Test plan, k-t-d:
1) Enable ILLModule
2) Create an ILL request for patron 'John', visit:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
3) Type 'John' and pick the only result. Set a type and library. Click 'Create'.
4) Go to patrons search and search for 'jo':
<staff_url>/cgi-bin/koha/members/members-home.pl
5) Tick the checkbox for John and someone else from the list. Click 'Merge selected patrons' from the top toolbar
6) Select the other patron (not John) as the 'patron to keep'. Click Merge patrons.
7) Notice '1 Illrequest was merge' message shows.
8) Visit the ILL list table, visit:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl
9) Notice the ILL request now belongs to the other patron, not John (who no longer exists).

Signed-off-by: Richard Bridgen <richard.bridgen@nhs.net>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:11 -07:00
Pedro Amorim 7277f04342
Bug 36360: Add Illrequest to list of stuff to consider merging
Signed-off-by: Richard Bridgen <richard.bridgen@nhs.net>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:10 -07:00
Nick Clemens 643da6f9cd
Bug 41206: Add collection code to transfers to receive
This patch adds the collection code to the transfers to receive report
It wraps it in a span for styling/hiding

To test:
1 - Transfer some books with collection code set to another branch
2 - Sign in as that branch
3 - Circulation -> Transfers to receive
4 - No collection code shown
5 - Apply patch
6 - Refresh
7 - See collection code

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:10 -07:00
Baptiste Wojtkowski fa23e8c145
Bug 39722: (follow-up) Remove find
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:09 -07:00
Baptiste Wojtkowski fac53ff21a
Bug 39722: Remove GetSuggestionFromBiblionumber from C4/Suggestions.pm
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:09 -07:00
Pedro Amorim 62db27bc7c
Bug 41281: Only skip metadata if empty or undefined
1) Enable ILLModule
2) Create an ILL request:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
3) Pick any type, add '42' for cardnumber and any library
4) Edit item metadata (assuming ID 1):
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?op=edititem&illrequest_id=1
5) Click 'Add new field' under 'Custom fields'
6) Add any value for 'key' e.g. "test" and '0' for value.
7) Click 'Update'. Notice the custom field doesn't show.
8) Apply patch. Refresh. Verify it shows now.

Signed-off-by: Richard Bridgen <richard.bridgen@nhs.net>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:08 -07:00
Baptiste Wojtkowski 5262ff688b
Bug 41695: Fix rejecter in suggestion.pm
TEST PLAN:
1 - Apply tests patch
2 - Run 'prove t/db_dependent/Koha/Suggestions.t' -> it fails on
rejecter
3 - Apply this patch
4 - Run 'prove t/db_dependent/Koha/Suggestions.t' -> it works

Note: this patch also provide tests for remaining accessors in the
subtest

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:08 -07:00
Baptiste Wojtkowski e99ad89f7c
Bug 41695: Unit tests
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:07 -07:00
Baptiste Wojtkowski 344d66e4d4
Bug 39721: Add test for accepter
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:07 -07:00
Baptiste Wojtkowski ba0b968a5a
Bug 39721: Changes suggestion_ref in order to remove it
suggestion_ref is quite messy, it needs to be reworked in order to use
objects in suggestion.pl.

As far as I studied, it is used as
- the whole input from forms,
- the default content to input in a form
- the content that will be amended in order to propose another hash to
  modSuggestion. However, ModSuggestion will be removed so we don't need
  such a hash
- every suggestion that need to be updated using the "change selected
  suggestion" tool
- create the object suggestion_only, whose purpose is still unclear to
  me.

As a result it is created by :
> 25 my $suggestion_ref = { $input->Vars };
And by removing a number of keys depending on the use of suggestion_ref
in the script.

In this patch, we remove every code made to use suggestion_ref as the
source object for form and use another variable, $stored_suggestion which is an object.

In next patch we aim at fully removing $suggestion_ref, which is still
used by ModSuggestion

Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:06 -07:00
Baptiste Wojtkowski 1a5bb27e8a
Bug 39721: Remove GetSuggestion from C4/Suggestions.pm
1 - Create suggestion
2 - Display a suggestion
3 - Amend a suggestion
To do with and without patch

Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>

Amended-by: Jonathan Druart
tidy

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:06 -07:00
Lucas Gass 3b149f0f50
Bug 33544: (RM follow-up) Add new line
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:05 -07:00
Laura_Escamilla 99e1ae7aa2
Bug 33544: (QA follow-up) fixed misspelled attribute
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:05 -07:00
Martin Renvoize 9954466c10
Bug 33544: Move biblio ID into title link
This patch removes the separate biblio_id column and instead embeds
the biblio link directly within the title field of the details column,
reducing table width while maintaining access to the full record.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:04 -07:00
Pedro Amorim 589c6112b4
Bug 33544: Update columns_settings.yml for consolidated details column
This patch removes the now-unused individual column definitions from
columns_settings.yml, replacing them with a single 'details' column
configuration.

Note: Fields combined into a single column are no longer individually
sortable. I believe this is acceptable for the ILL requests fields selected
for display consolidation to reduce horizontal overflow.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:04 -07:00
Martin Renvoize 9a3a9515bb
Bug 33544: Combine title details into single column
This patch consolidates the separate title details fields (title,
author, year, etc.) into a single combined column in the ILL requests
table for improved display and reduced horizontal scrolling.

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:03 -07:00
Owen Leonard 290db21792
Bug 41350: Terminology: Biblio was already issued
This patch updates the string found in action logs, "Biblio was already
issued" replacing it with "An item from this bibliographic record is
already checked out."

To test, apply the patch and go to Administration -> System preferences.

- Set the AllowMultipleIssuesOnABiblio preference to "Don't allow."
- Locate a bibliographic record with more than one available item.
- Check out one of the items to a patron, then another.
- You should be asked for confirmation, "Patron has already checked out
  another item from this record. Check out anyway?"
  - Confirm the checkout.
- Go to Tools -> Log viewer and set both "Display from" and "Display to"
  set to today, and submit.
  - In the log you should see an entry containing the updated string:
    "An item from this bibliographic record is already checked out"

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:03 -07:00
Owen Leonard b1c004c064
Bug 41677: Use template wrapper for tabs: OAI repositories
This patch updates the OAI repositories search include to use WRAPPERs
for the Bootstrap tabs markup.

To test, apply the patch and go to Administration -> OAI repositories.

- The search form in the header should have three options: Search OAI
  repositories, Check out, and Search catalog.
- Each tab should activate correctly, and each search form should should
  work as expected.*
  * Note that the OAI repositories form searches by repository name
    using a "Starts with" search.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:02 -07:00
Tomás Cohen Arazi 8895b74bee
Bug 41619: Add Koha::CSV
This patch introduces Koha::CSV, a wrapper around Text::CSV_XS that
provides consistent defaults and methods for CSV generation across Koha.

The class:
- Inherits CSV delimiter from CSVDelimiter system preference
- Enforces security defaults (binary=1, formula='empty')
- Provides convenient methods for common CSV operations
- Allows per-instance configuration overrides

This establishes a foundation for standardizing CSV exports throughout
Koha, eliminating inconsistencies in CSV generation and providing a
testable, maintainable approach to CSV handling.

Test plan:
1. Apply patch
2. Run:
   $ ktd --shell
  k$ prove t/Koha/CSV.t
=> SUCCESS: Tests pass!
3. Tests cover:
   - Default initialization with CSVDelimiter preference
   - Preference value inheritance (comma, semicolon, tabulation)
   - Constructor parameter overrides
   - add_row() method with various data types
   - combine() and string() methods
   - print() to filehandle
   - Proper quote escaping and empty field handling
4. Sign off :-D

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-02-06 15:17:02 -07:00
Pedro Amorim ab14eb2bca
Bug 41214: (QA follow-up): Tidy fix
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:01 -07:00
Matt Blenkinsop 76d7a0bc72
Bug 41214: Apply the same logic to table columns
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:01 -07:00
Matt Blenkinsop 4c316e25cd
Bug 41214: Convert hideIn to a callback to allow total removal of fields via syspref
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:00 -07:00
Pedro Amorim 0c0f4cc360
Bug 41214: Add cypress test
cypress run --spec t/cypress/integration/SIP2/Accounts.ts --config video=false,screenshotOnRunFailure=false

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:17:00 -07:00
Pedro Amorim 4544e70b37
Bug 41214: Only show cash register if UseCashRegisters sys pref is enabled
Test plan:
1) Add a new account:
http://localhost:8081/cgi-bin/koha/sip2/accounts/add
2) Notice the 'cash register' field does not show
3) Enable UseCashRegisters sys pref
4) Repeat 1 (or edit an existing account). Refresh the page.
5) Notice the 'cash register' field now shows

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 15:16:51 -07:00
Lucas Gass 115629bab3
Bug 39438: (RM follow-up): Fix failing test
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 10:43:09 -07:00
Lucas Gass 2aaf585013
Revert "Bug 41173: Fix ERM breadcrumb causing page reload"
This reverts commit 7a614e4c67.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 09:58:58 -07:00
Lucas Gass 071ed864cf
Revert "Bug 41173: Add a Cypress test"
This reverts commit 8af1ad8688.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 09:58:51 -07:00
Kyle M Hall 6592be6a08
Bug 41781: Holds queue builder ( build_holds_queue.pl ) fails if HoldsQueueParallelLoopsCount is greater than 1
If HoldsQueueParallelLoopsCount is set to a value greater than 1, build_holds_queue.pl will fail with the message:

Can't locate object method "new" via package "Parallel::ForkManager" (perhaps you forgot to load "Parallel::ForkManager"?) at /kohadevbox/koha/C4/HoldsQueue.pm line 205.

Test Plan:
1) Run misc/cronjobs/holds/build_holds_queue.pl
2) Note the error message
3) Run misc/cronjobs/holds/build_holds_queue.pl
4) Note the error message is gone!

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 07:21:03 -07:00
Lucas Gass 928abfcdc2
Bug 39916: (RM follow-up) Tidy
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 07:21:02 -07:00
Paul Derscheid 37c8b7544e
Bug 39916: (QA follow-up) Fix startRange class lost on month navigation
When selecting a booking start date that triggers month navigation
(e.g., selecting Feb 1 while viewing January), the flatpickr calendar
would lose the 'startRange' CSS class on the selected date. This
occurred because setting maxDate in the onChange handler triggered a
calendar redraw, and flatpickr only applies startRange during the
initial click event, not during redraws.

This caused the Cypress test "should handle date range validation
correctly" to fail on dates near month boundaries (e.g., January 30).

The fix re-applies the startRange class after the maxDate is set,
using setTimeout to ensure it runs after the redraw completes. A
guard ensures this only happens when exactly one date is selected,
preventing interference with end date selection.

To test:
- Apply patch
- Open a biblio detail page with bookable items
- Click "Place booking"
- Select a patron, pickup location, and item
- In the date picker, click a date that's in the next month
  (visible in the "next month" area of the calendar)
- Verify the selected date shows the startRange styling (highlighted
  as the range start)
- Select an end date
- Verify the range is properly selected and the form can be submitted
- Run Cypress tests:
  yarn cypress run --spec t/cypress/integration/Circulation/bookingsModalDatePicker_spec.ts
- Verify all tests pass

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 07:21:01 -07:00
Matt Blenkinsop ca3c191d08
Bug 39438: (follow-up) Remove agreement_id from periods before comparing to result
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-06 07:21:01 -07:00
Jonathan Druart 849f67988f
Bug 41076: Adjust our perl tidiness for v20250105
Generated using
  `perl misc/devel/tidy.pl --perl`

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-02-05 09:03:51 -07:00
Pedro Amorim f06c5fc5e6
Bug 39971: (QA follow-up): Tidy fix
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:26 -07:00
Nick Clemens 76378efa72
Bug 39971: (QA follow-up) Unit tests
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:25 -07:00
Pedro Amorim 9a4e6a87df
Bug 39971: Add query param
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:25 -07:00
Pedro Amorim 20e00d408f
Bug 39971: Add POD
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:24 -07:00
Pedro Amorim e0824a79bf
Bug 39971: Move patron_attributes_form method to a class
This makes it reusable, documentable, and testable.

This is changing code without adding a new feature or fixing a bug.
The first test plan should be to just make sure that the existing functionality is kept intact, i.e. rendering and saving the patron attribute types in the form.

As to why we need this, we're maintaining the ILL Actions plugin where we have a feature to quickly add a patron. This is designed to be as minimal as possible, but if there are any mandatory patron attribute types, those need to be considered.
To test this:
1) Apply patches and enable ILLModule syspref.
2) Install the plugin:
https://github.com/openfifth/koha-plugin-ill-actions/releases/tag/v2.4.0
3) Add a mandatory patron attribute type, you can edit the existing one in k-t-d, visit:
   <staff_url>/cgi-bin/koha/admin/patron-attr-types.pl?op=edit_attribute_type&code=SHOW_BCODE
5) Tick the 'Staff interface mandatory' checkbox
6) Create a new ILL request, visit:
   <staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
7) Notice there's a '+Add new user' link next to the cardnumber input at the bottom of the form. Click that.
8) Notice there's a 'Additional attributes and identifiers' section. This is only possible (without writing it again) because of the patches here.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:24 -07:00
Pedro Amorim b57ff25c9a
Bug 39971: Move form rendering to reusable tt block
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:23 -07:00
Lucas Gass 26c8ed7d8f
Bug 30331: DBRev 25.12.00.018
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:23 -07:00
Lucas Gass 2428d00989
Bug 30331: Update tests, mock the system preference
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:22 -07:00
Lucas Gass 2df89cf721
Bug 30331: Update system preference descriptions
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:22 -07:00
Lucas Gass d1ec0e47b2
Bug 30331: Unit tests
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:21 -07:00
Lucas Gass 2817e8f930
Bug 30331: Implement manual and automatic renewal base period
To test:

1. APPLY PATCH, updatedatabase, restart_all
2. Go to your circulation rules and do the following:
    -Automatic renewal: Yes
    -No renewal before: 5
    -No automatic renewal before: 5
    -Renewal period: 5

3. Set ManualRenewalPeriodBase to "the old due date of the checkout".
4. Set  AutomaticRenewalPeriodBase to "current date".

5. Check out an item and set the due date to 2 days from today
6. Attempt to manually renewal the item.
7. Notice the new date is calculated based on the old checkout date.
8. Check out an item another item and set the due date to 2 days from today.
9. Run `perl /kohadevbox/koha/misc/cronjobs/automatic_renewals.pl -c -v`
10. Notice the new date is calculated based on the current date.

11. Set ManualRenewalPeriodBase to "current date".
12. Set  AutomaticRenewalPeriodBase to "the old due date of the checkout".

13. Check out an item and set the due date to 2 days from today
14. Attempt to manually renewal the item.
15. Notice the new date is calculated based on the current date.
16. Check out an item another item and set the due date to 2 days from today.
17. Run `perl /kohadevbox/koha/misc/cronjobs/automatic_renewals.pl -c -v`
18. Notice the new date is calculated based on the old checkout date.

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:21 -07:00
Lucas Gass 15d83804b3
Bug 30331: Rename RenewalPeriodBase as ManualRenewalPeriodBase and add AutomaticRenewalPeriodBase sys pref
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:20 -07:00
Martin Renvoize e58d39671f
Bug 39584: Fix end date disable to check selected item, not all items
When a specific item is selected in the booking modal, the end date
disable logic was incorrectly checking if ALL bookable items were
unavailable before disabling a date. This meant dates with existing
bookings for the selected item remained selectable if other items
were available.

Now when a specific item is selected (booking_item_id != 0), the
logic checks if that specific item is in the unavailable_items list.
The "all items unavailable" check is only used when "Any item" is
selected.

This aligns the end date selection behavior with the start date
selection behavior, which already correctly checks only the selected
item.

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:20 -07:00
Martin Renvoize 22c8829738
Bug 39584: Fix trail period incorrectly limiting max booking date
This commit fixes a bug where the trail period was preventing selection
of the maximum end date allowed by circulation rules, even when there
were no booking conflicts in the trail period.

The Problem:
============
The hover logic in place_booking.js checked if ANY disabled date fell
within the trail period and would prevent selection. However, dates can
be disabled for multiple reasons:
1. They conflict with an existing booking (correct reason to disable)
2. They are beyond the maxDate set by circulation rules (incorrect!)
3. They are in the past

When hovering over the max selectable end date, the trail period would
extend beyond the maxDate. Those dates were disabled by flatpickr
automatically, and the code incorrectly interpreted this as a booking
conflict, preventing selection of the otherwise valid max date.

The Fix:
========
Modified the trail period conflict check (lines 995-1005) to only
consider dates that are within the max date range. Disabled dates
beyond the maxDate are now ignored when checking for trail conflicts.

Before:
  if (elemDate.isAfter(trailStart) && elemDate.isSameOrBefore(trailEnd)) {
      trailDisable = true;
  }

After:
  if (elemDate.isAfter(trailStart) && elemDate.isSameOrBefore(trailEnd)) {
      const maxDate = periodPicker.config.maxDate ? dayjs(periodPicker.config.maxDate) : null;
      if (!maxDate || elemDate.isSameOrBefore(maxDate)) {
          trailDisable = true;
      }
  }

This ensures trail periods only prevent selection when they conflict
with actual bookings, not when they extend beyond the circulation
rules' maximum date.

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:19 -07:00
Martin Renvoize 9284a43817
Bug 39584: Add Cypress tests for bookings lead and trail period behavior
This commit adds comprehensive Cypress tests for the lead and trail period
functionality in the bookings modal date picker. These tests validate the
hover effects, conflict detection, and date selection constraints.

The tests use cy.clock() to fix "today" to June 10, 2026, enabling
deterministic testing without date arithmetic complications.

Test coverage includes:
- Lead period visual hints (CSS classes on hover)
- Lead period conflict prevention with past dates
- Lead period conflict prevention with existing bookings
- Trail period visual hints (CSS classes on hover)
- Trail period conflict prevention with existing bookings
- Max date selection when trail period is clear (no false restrictions)

Test setup:
- Fixed date: June 10, 2026
- Lead period: 2 days
- Trail period: 3 days
- Issue length: 3 days with 2 renewals of 2 days each
- Max booking period: 7 days
- Blocker booking: June 25-27 (tests both lead and trail conflicts)

Test phases:
1. Hover June 13 - lead period (June 11-12) is clear, no leadDisable
2. Select June 13, hover June 16 - trail period (June 17-19) is clear
3a. Hover June 11 - lead period includes past date (June 9), leadDisable
3b. Hover June 29 - lead period overlaps blocker (June 27), leadDisable
4. Select June 20, hover June 23 - trail overlaps blocker, trailDisable
5. Select June 13, hover June 20 (max) - trail is clear, selectable

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:19 -07:00
Lucas Gass a4208f711b
Bug 40364: DBRev 25.12.00.017
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:18 -07:00
Lucas Gass 7c82ec83b7
Bug 40364: Add new permission to t/Koha/Auth/Permissions.t
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:18 -07:00
Lucas Gass dfcc095270
Bug 40364: (follow-up) Fix duplicated mapping key issue
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:17 -07:00
Andrew Fuerste Henry 433b5e0f3f
Bug 40364: Correct wording in user permissions
Changes the descriptions of the holds history and checkout history permissions to say "view" rather than "viewing"

Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:17 -07:00
Andrew Fuerste Henry f5b8ba029e
Bug 40364: Update permissions for patron_holds API path
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:16 -07:00
Andrew Fuerste Henry 872fdd01f4
Bug 40364: Correct system preference check in atomicupdate
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:16 -07:00
Andrew Fuerste Henry 6e1065e840
Bug 40364: Implement holds history permission
To test:
1 - enable IntranetReadingHistoryHolds system preference
2 - make some holds for a patron, confirm you can see Holds History in the patron record when logged in as a superlibrarian
3 - log in as a non-superlibrarian with edit_borrowers permission, confirm you can see holds history in pthe patron record
4 - apply patch, updatedatabase, restat_all
5 - confirm you can see holds history as a superlibrarian
6 - confirm your non-superlibrarian from step 3 was given view_holds_history permission and can view holds history
7 - remove view_holds_history permission from your user, confirm you can no longer access holds history

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:15 -07:00
Andrew Fuerste Henry 360fe6ba9a
Bug 40364: Atomicupdate to add permission
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:15 -07:00
Lucas Gass 650f4ad73d
Bug 32682: DBRev 25.12.00.016
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:14 -07:00
Lucas Gass 48c9bdc86a
Bug 32682: Fix how sys pref is retrived in DB update
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:14 -07:00
Lucas Gass 2b89780db4
Bug 32682: Update patrons chekcoput route to allow access with new permission
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:13 -07:00
Lucas Gass b32c0e8d96
Bug 32682: Add new permission to t/Koha/Auth/Permissions.t
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:13 -07:00
David Flater 3a2f84f0cd
Bug 32682: Implement reading history permission
To test:
1. Create or modify a staff user with edit_borrowers permssion enabled ( Staff A )
2. Create or modify a staff user without edit_borrowers permssion enabled ( Staff B )
3. Ensure the "intranetreadinghistory" syspref is enabled
4. Check out an assortment of items to any patron
5. Apply patch, update_database, then restart_all
6. Check the permissions of the staff user A and ensure that they have been automatically assigned the new "view_checkout_history" permission
7. Check the permissions of the staff user B and ensure that they have NOT been automatically assigned the new "view_checkout_history" permission
8. View the same patron and ensure you can still see their checkout history as Staff A and as a superlibrarain
9. Disable the "intranetreadinghistory" syspref and remove the "view_checkout_history" permission of the staff user

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:12 -07:00
David Flater 7caf73d3c5
Bug 32682: Add permission for viewing patron reading history
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>
Signed-off-by: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:12 -07:00
Jonathan Druart e7325583f3
Bug 39438: (bug 38446 follow-up) Add agreement_period to /erm/extended_attribute_types
Sponsored-by: Karlsruhe Institute of Technology (KIT)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:11 -07:00
Martin Renvoize b44f85e1b2
Bug 39438: Add DBIC schema relationships for agreement period additional fields
This patch adds the required DBIx::Class relationship definitions to
Koha::Schema::Result::ErmAgreementPeriod to support additional fields
and extended attributes on agreement periods.

Two has_many relationships are added:
- additional_field_values: links to all additional field values
- extended_attributes: alias for additional_field_values

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:11 -07:00
Matt Blenkinsop 080fccc0c5
Bug 39438: Add additional fields support to ERM agreement periods
This patch enables additional fields functionality for agreement periods
in the ERM (Electronic Resource Management) module, allowing libraries
to define custom metadata fields for agreement period records.

Changes include:
- Update Koha::AdditionalField to support erm_agreement_periods table
- Modify API definitions to include extended_attributes in responses
- Add erm_agreement_periods option to additional fields admin interface
- Enhance Vue components to display and manage additional field values
- Update ShowElement component to handle period-specific attributes

Test plan:
1. Navigate to Administration > Additional fields
2. Create new fields for "Agreement periods"
3. Edit an ERM agreement and add/edit period information
4. Verify additional fields appear and can be populated
5. Confirm values are saved and displayed correctly

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:10 -07:00
Lari Taskula bee9d8da78
Bug 40933: Add the ability to send custom SMS messages to a patron
To test:
1. Make sure SMSSendDriver system preference is enabled
2. In staff client, navigate to patron details of any patron
3. From the members menu toolbar, click "Add Message"
4. Under "Add a message for", select "SMS - sms number of patron"
5. Write a custom message
6. Click "Save"
7. Navigate to patron's Notices tab
8. Observe a new notice titled "SMS added by a librarian"
9. Click it
10. Observe the content you wrote in step 5
11. Repeat steps 2-4
12. Select predefined patron notice. If you don't have one,
navigate to Tools > Notices and slips > New notice > Patrons (custom message)
and add content under sms delivery option
13. Repeat steps 6-7
14. Observe your custom message in the notices table
15. Observe your custom message content when you click the notice

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>

Signed-off-by: Charlie Arthur <carthur@slolibrary.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:10 -07:00
Owen Leonard 4ddbf50529
Bug 40060: Update structure of popup windows in the OPAC
This patch updates templates for OPAC popup windows to add a
fixed "navbar" element at the top of the window with the main heading at
an "X" button which closes the window.

To test, apply the patch and rebuild the OPAC CSS. On each page, test
that the pop-up window looks correct. Confirm that the "X" in the upper
right works to close the window.

- Clear your browser cache if necessary.
- Make sure the opacbookbag, virtualshelves, and OPACComments system
  preferences are enabled.
- Log in to the OPAC and perform a catalog search which will return
  multiple results.
- Select multiple results and choose Add to list -> New list.
  - Test Add to list -> More lists
  - Test Add to list -> (existing list)
- View the contents of a list.
  - Click "Send list" and test the process of sending a list.
- Return to your catalog search results and add multiple items to the
  cart.
  - View the cart
  - Test the "Send" option
- With the OPACComments system preferenc enabled, view the detail page
  for a bibliographic record.
  - Under the comments tab, click "Post your comments..."

Sponsored-by: Athens County Public Libraries
Signed-off-by: Ludovic Julien <ludovic.julien@inLibro.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:09 -07:00
Nick Clemens 83e553dd9e
Bug 40905: (QA follow-up) Apply same fix for exceptions table
This table also has a show past holidays button, we need the same fix

To test:
1 - Spam holidays into both tables
    You can run the SQL commands below a few times each
INSERT INTO special_holidays (branchcode, day, month, year,isexception, title,description) VALUES ('CPL',FLOOR(RAND() * 28) + 1,FLOOR(RAND() * 12) + 1,FLOOR(RAND() * 6) + 2020,0,LEFT(UUID(),8),UUID());

INSERT INTO special_holidays (branchcode, day, month, year,isexception, title,description) VALUES ('CPL',FLOOR(RAND() * 28) + 1,FLOOR(RAND() * 12) + 1,FLOOR(RAND() * 6) + 2020,1,LEFT(UUID(),8),UUID());

2 - Go to Tools->Calendar
3 - Click 'Show past holidays' for exceptions and unique holidays
4 - Confirm checking and unchecking work on both tables

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:09 -07:00
Slava Shishkin 27b89b20b8
Bug 40905: Fix "Show past entries" checkbox in unique holidays
Test plan:
1. Go to Tools → Calendar.
2. Select a branch with past unique holidays.
   If you don't have past closed days: Create a new holiday in a past
   month or so.
3. In the Unique holidays section, enable "Show past entries".
4. Past entries are not displayed.
5. Apply patch:
6. Repeat the steps above.
7. Past entries are displayed immediately.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:08 -07:00
Martin Renvoize b322e3e959
Bug 40769: Display hold fees in staff client when placing holds
This patch adds hold fee information display in the staff client's hold
request interface, bringing it to feature parity with the OPAC.

The OPAC already shows patrons the fee that will be charged for placing
a hold, but the staff client did not display this information when staff
place holds on behalf of patrons. This creates a transparency gap where
staff cannot inform patrons about potential charges.

Changes:
- Import GetReserveFee function in reserve/request.pl
- Calculate reserve charges for each biblio record
- Display fee information in both single and multi-hold interfaces
- Show appropriate messaging based on HoldFeeMode system preference
- Use consistent formatting with currency display

Test Plan:
1. Set up hold fees:
   - Go to Administration > Patron categories
   - Edit a patron category and set a "Hold fee" (e.g., 1.00)
   - Ensure HoldFeeMode system preference is set (default: "only if all
     items are checked out and the record has at least one")

2. Test single hold with fee display:
   - Go to a bibliographic record
   - Click "Place hold"
   - Find a patron from the category with hold fees configured
   - Verify fee information displays after the title:
     * Shows "Note: [Patron Name] will be charged a hold fee of $1.00
       for placing this hold" when the HoldFeeMode is set to "any time
       a hold is placed"
     * Or "...when they collect this item" if HoldFeeMode is set to "any
       time a hold is collected"

3. Test multi-hold with fee display:
   - Ensure 'DisplayMultiPlaceHold' preference is set to 'Enable'
   - Search the catalog
   - On the search results page, select multiple biblios and click
     'Place hold'
   - Search for the same patron
   - In the confirmation table, verify each record shows:
     * "Hold fee: $1.00 (charged when placed)" in the Information column
     * Or "(charged when collected)" based on HoldFeeMode

4. Test no fee display:
   - Repeat tests with a patron from a category with no hold fee
   - Verify no fee information is displayed

5. Test different HoldFeeMode settings:
   - Change HoldFeeMode system preference to 'any_time_is_collected'
   - Verify messaging changes to "when they collect this item"
   - Test with 'not_always' mode and verify fee only shows when appropriate

6. Verify existing functionality:
   - Ensure hold placement still works normally
   - Verify all other hold interface elements remain unchanged
   - Test that holds are properly placed with or without fees

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-01-30 10:54:08 -07:00
Pedro Amorim fd87ee6edf
Bug 41512: (OPAC) Additional fix
This is needed or else the OPAC still throws the 'malformed query string' mentioned on the original test plan.
This is because the fix from bug 40865 was only applied to intra datatables.js not the OPAC datatables.

I don't know why this fixes it for the OPAC, but it does.

Joubu, thoughts?

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:07 -07:00
Pedro Amorim 20d20ba9a9
Bug 41512: Fix ILLCheckAvailability
1) Install the unpaywall plugin:
  https://github.com/openfifth/koha-plugin-ill-avail-unpaywall/releases/tag/1.0.2
2) Configure an email address on the plugin configuration (I believe any email address should work):
<staff_url>/cgi-bin/koha/plugins/run.pl?class=Koha%3A%3APlugin%3A%3ACom%3A%3APTFSEurope%3A%3AAvailabilityUnpaywall&method=configure
3) Enable ILLModule and ILLCheckAvailability sys prefs
4) Create a new ILL request, visit:
  <staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
5) Pick type: journal article and enter the DOI: 10.1002/bjs.1800621024
6) Enter a cardnumber e.g. '42' for koha user and any library.
7) Notice the availability screen never renders, a console error is logged on the browser.
8) Apply patch. Refresh. Repeat.

This appears to have stopped working after bug 26553. Using git bisect, after bug 26553 (cc09868152), this started returning a 'bad request: malformed query string' because query params were being sent to the plugin API route (which doesn't consider them). Bug 40865 then fixed the bad request error, but resulted in the console error we see now, which is fixed by this patch.
ajax.dataSrc appears to now be returning an additional level of 'data' due to datatables upgrade (?), this patch changes the dataSrc override to be more in line with the example doc at https://datatables.net/reference/option/ajax.dataSrc

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:07 -07:00
Aya Khallaf bc2d8dc1c3
Bug 40703: Replaced data-toggle with data-bs-toggle
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:06 -07:00
Pedro Amorim 6473a06916
Bug 41478: Refactor: Readability + Never nester
1) Install the following dummy backend plugin:
https://github.com/openfifth/koha-ill-backend-plugin/releases/tag/v2.0.9
2) Sys prefs preparation: Enable ILLModule, ILLOpacUnauthenticatedRequest and check PluginBackend on AutoILLBackendPriority
3) Logout, create a new Unauthenticated ILL request on the OPAC, visit:
<opac_url>/cgi-bin/koha/opac-illrequests.pl?op=create&backend=Standard
4) Fill in the mandatory top fields (first name, last name, email address and library)
5) Pick 'Book' for type and enter 'green' in title. This will cause the dummy plugin to be available in the AutoILL logic.
6) At the bottom of the form, on the "Verification" captcha, purposely input a wrong captcha e.g. '123'
7) Click 'Create'. Notice an odd looking form is presented, this is the dummy backend form, but it should present the 'Standard' form when using AutoILLBackendPriority as that was also the form we were presented with originally.
This happens because currently it's the backend that delegates back to Koha what template to render. When using AutoILLBackendPriority, however, Koha should handle this template delegation instead and present the Standard form.
8) Apply patches. Repeat test plan. Notice that now on step 7 you get the correct form, and error message (coming from Standard, as expected when using AutoILLBackendPriority).
9) Input the correct captcha this time
10) Confirm the created request landed in the PluginBackend backend.

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-01-30 10:54:06 -07:00
Pedro Amorim 53a78c501d
Bug 41478: Render Standard form on OPAC if failed_captcha and AutoILLBackendPriority
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-01-30 10:54:05 -07:00
Pedro Amorim 51c718b184
Bug 41465: Fix display of 'type' for unauthenticated ILL request
'request' is unblessed in this context. get_type doesnt exist/work.

Test plan:
1) Enable ILLModule and ILLOpacUnauthenticatedRequest
2) Logout, place an OPAC unauthenticated ILL request, visit:
<opac_url>/cgi-bin/koha/opac-illrequests.pl?backend=Standard&op=add_form
3) Input all mandatory fields. Click 'Create'.
4) Notice 'Request type' shows as 'N/A'. This will always be the case regardless of type you pick.
5) Apply patch. Repeat. Notice 'Request type' now shows as expected.

Signed-off-by: Richard Bridgen <richard.bridgen@nhs.net>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:05 -07:00
Paul Derscheid 1b52f621dc
Bug 41035: remove invalid path component 'item'
To test:
1. Create a record as a collection with leader 007 set to c
2. Add some items on the same branch to the bundle
3. Check one item in
4. Observe that you get a 404 error and the item is not removed from
   the bundle
5. Apply the patch
6. Repeat step 3
7. Observe that the item is correctly removed (unlinked) from the
   bundle
8. Sign off

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:04 -07:00
Lucas Gass af6e9f7063
Bug 28308: DBRev 25.12.00.015
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:04 -07:00
Jonathan Druart 0a42444fee
Bug 28308: DBrev
To remove the unnecessary entries from the DB

Signed-off-by: Ayoub Glizi-Vicioso <ayoub.glizi-vicioso@inLibro.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:03 -07:00
Jonathan Druart ac724b8f06
Bug 28308: JS changes
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:03 -07:00
Jonathan Druart d2ad74b63c
Bug 28308: Remove 0 from "days in advance" for "Advance notice"
0 days in advance means that the notice should not be sent.

This patch contains the pl/pm changes: we set to null if 0 is passed,
and do not insert a row in borrower_message_transport_preferences

Test plan:
0. Do not apply the patches
1. Create some entries with email and days in advance = 0
2. Create some entries with email and days in advance > 0
3. Apply the patches
4. Run the updatedatabase script
=> The rows in borrower_message_transport_preferences have been removed
for days in advance = 0
5. Edit some messaging preferences
=> 0 has been replace with a blank entry
=> checkboxes are disabled when blank is selected
6. Save
=> No new entry in borrower_message_transport_preferences is blank is
selected
7. advance_notices.pl still works correctly

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:02 -07:00
Emmi Takkinen 4e6e7bd914
Bug 29768: Do not hide guarantor name from memberentry.pl
Syspref "hidepatronname" currently hides guarantors
name on memberentry.pl. This is inconsistent since name isn't hidden e.g. on moremember.pl and staff
can already see patrons other personal information
during edit.

To test:
1) Disable syspref "hidepatronname"
2) Edit guarantee patrons information
=> Field "Guarantor" only shows guarantors cardnumber (or
is empty if guarantor has no cardnumber)
3) Apply patch
=> Guarantors name is now displayed (as a hyperlink)

Also confirm  hidepatronname still works as expected
on other pages (holds table, items detail page etc.).

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:02 -07:00
Jonathan Druart d94e2b26b6
Bug 36136: Make sure 'to' dates are instantiated with the 'from' dates as minimum
To test you should check the behavior of forms which have sets of "from"
and "to" dates. It shouldn't be possible to select a "to" date which is
before the "from" date. For example:

- Overdues report
- Invoice search

In some cases we need to check that the fields work correctly when the
"from" date is already filled:

- Editing vendor issues
- Editing a budget
- Duplicating a subscription

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Olli Kautonen <bhm503@myy.haaga-helia.fi>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:01 -07:00
Jonathan Druart 8af1ad8688
Bug 41173: Add a Cypress test
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:01 -07:00
Tomas Cohen Arazi 7a614e4c67
Bug 41173: Fix ERM breadcrumb causing page reload
The main ERM route was missing a 'name' property, causing the
navigation store to treat it as an external path (.pl extension).
This resulted in breadcrumb links using href instead of router-link,
causing unwanted page reloads.

To test:
1. Navigate to ERM module
2. Go to any sub-section (Agreements, Licenses, etc.)
3. Click on 'E-resource management' in the breadcrumb
=> FAIL: Notice the page reloads!
4. Apply the patch
5. Repeat steps 1-3
=> SUCCES: Verify the page doesn't reload and navigation works smoothly
6. Sign off :-D

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:54:00 -07:00
Owen Leonard ead9dd4cac
Bug 32288: Capitalization: RDA Carrier, etc.
This patch corrects the capitalization of the phrases "RDA carrier,"
"RDA content," and "RDA media," found in several UNIMARC cataloging
plugins.

To test you could edit subfields in a MARC framework to activate each
affected plugin, but I think the change is simple enough to be checked
by examining the patch.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-30 10:53:52 -07:00
David Cook c3169205ec
Bug 41662: Prevent CSRF on opac-patron-consent.pl
This change prevents CSRF on opac-patron-consent.pl by
adding checking of the "op".

Test plan:
0. Apply the patch and koha-plack --restart kohadev
1. Set system preference PrivacyPolicyConsent to "permissive"
2. Log into the OPAC and go to this path:
/cgi-bin/koha/opac-patron-consent.pl?check_GDPR_PROCESSING=1
3. Note that it doesn't change the consent
4. Go to this path:
/cgi-bin/koha/opac-patron-consent.pl?check_GDPR_PROCESSING=0
5. Note that it doesn't change the consent
6. Click the "Yes" radio button and click "Save"
7. Note it changes the consent to Yes
8. Click the "No" radio button and click "Save"
9. Note it changes the consent to No

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-29 15:11:21 -07:00
Lisette Scheer c1d863902a
Bug 41593: (QA follow-up) Move cypress test
This patch moves the cypress test into the Acqusitions folder to help keep t/cypress/integration organized as we add more tests.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-29 15:11:21 -07:00
Jonathan Druart 1e5d8703da
Bug 41593: Add a Cypress test
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-29 15:11:20 -07:00
David Cook 690bd0568f
Bug 41593: Validate displayby parameter in suggestion.pl
This change validates the displayby parameter in suggestion.pl
in order to prevent SQL injection.

Test plan:
0. Do not apply the patch
1. Log into the staff interface
2. Visit the path /cgi-bin/koha/suggestion/suggestion.pl?
op=else&displayby=status+AND+EXTRACTVALUE(1,CONCAT(0x7e,@@version))
3. Notice a 500 error is generated
4. In the error logs, the MySQL version number will appear

5. Apply the patch
6. koha-plack --restart kohadev

7. Visit the path /cgi-bin/koha/suggestion/suggestion.pl?
op=else&displayby=status+AND+EXTRACTVALUE(1,CONCAT(0x7e,@@version))
8. Notice that no error is generated
9. Try each of the "Organize by" options on the left nav bar
10. Note that none of them generate an error and that the option is
retained after clicking "Go"

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-29 15:11:12 -07:00
Owen Leonard 64796fde35
Bug 41586: Fix spacing problem in display of patron names
This patch updates patron-title.inc which generates the output of patron
names throughout Koha. Some whitespace is added to the display of
borrowers.othernames and spaces removed from the maybe_span WRAPPER.

To test, apply the patch and create or edit a patron in the staff
client.

- Add a value to all the name fields: Surname, first name, preferred
  name, middle name, and other name.
- Save the patron and check the display of their name on the patron
  detail page. The name in the page heading and sidebar should have
  correct spacing.
- Place a hold for that patron to be picked up at the logged-in library.
- Go to Circulation -> Holds to pull and confirm that the patron name is
  displayed correctly (this tests the display when the format is
  lastname, firstname).
- Edit the same patron and change its category to an organizational one
  (Inter-Library Loan, Library, or School in the sample data).
- View the same pages listed above and confirm that the name displays
  correctly.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Manvi <manvisharma1006@gmail.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:06 -07:00
Nick Clemens 91a78d1aec
Bug 41700: Unit test
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:05 -07:00
Nick Clemens 1c60059808
Bug 41700: Update note_date to datetime
This patch updates the swaager definition to have the correct format

To test:
1 - Enable issue notes:  AllowCheckoutNotes preference
2 - Issue an item to a patron
3 - Sign in to opac as that patron, add a note
4 - Have staff client open and a second tab try:
    http://localhost:8081/api/v1/checkouts/
5 - Error "message":"Does not match date format.","path":"\/body\/0\/note_date"
6 - Apply patch, yarn build, restart all
7 - Refresh the page
8 - Success!

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:05 -07:00
Nick Clemens b034685fe8
Bug 41315: Don't change which branch is used for getting the transports
See bug 41316 - the script uses the triggers of the issuing branch when sending
by patron library - we shouldn't change this here, but on the next bug

Signed-off-by: Ben Daeuber <bdaeuber@fargolibrary.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:04 -07:00
Nick Clemens 37eda84469
Bug 41315: (follow-up) Rename statements for clarity
Signed-off-by: Ben Daeuber <bdaeuber@fargolibrary.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:04 -07:00
Nick Clemens ce848197e5
Bug 41315: Remove unecessary conditional
We are in a loop here:
foreach my $branchcode (@branches) {

branchcode must be defined or something has gone seriously wrong with PERL

Signed-off-by: Ben Daeuber <bdaeuber@fargolibrary.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:03 -07:00
Nick Clemens 914c8be2c0
Bug 41315: Change only notice branchcode when OverDueNoticeFrom is set to patron-homelibrary
This patch adds a new variable $notice_branchcode and uses this when getting the patron's
homelibrary to ensure we don't change the branch we are using.

To test:
 1 - At branch A - checkout three items, 1 day overdue to 3 patrons of the same category from different branches (not A)
 2 - Set the system preference OverdueNoticeFrom to 'patron home library'
 3 - Browse to More->Tools->Overdue notice/status triggers
 4 - Set overdue triggers for default library and the category used above to:
     Delay: 1
     Letter: Overdue notice (ODUE)
 5 - From the command line run the overdue notices:
     perl misc/cronjobs/overdue_notices.pl --triggered --library CPL --test --nomail -v -v
 6 - Note that we begin pass 1 with CPL and then use a different branch for passes 2 and 3
 7 - Apply patch
 8 - Repeat
 9 - Confirm branch is consistent
10 - Run without branch param:
     perl misc/cronjobs/overdue_notices.pl --triggered --library CPL --test --nomail -v -v
11 - Confirm branches are consistent

Signed-off-by: Ben Daeuber <bdaeuber@fargolibrary.org>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:03 -07:00
Paul Derscheid 1e4b9e33c7
Bug 39916: (QA follow-up) Fix typo in test comment
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:02 -07:00
Martin Renvoize 6c63cb90fa
Bug 39916: Fix booking modal race conditions and redraw issues
This patch addresses two critical issues in the booking modal:

1. Race condition in edit mode:
   When editing a booking, the item selection would sometimes fail to
   properly initialize because the item options weren't fully loaded
   with their data attributes. This caused the itemtype field to not
   auto-populate correctly.

   Fixed by moving the item pre-selection to occur after a brief delay
   and ensuring the select2:select event is triggered with proper data
   parameters including the element reference.

2. Flatpickr redraw issue:
   The datepicker would lose its disabled dates after certain operations
   because the disable configuration wasn't being reapplied during redraws.

   Fixed by explicitly setting the disable configuration before calling
   redraw() to ensure disabled dates are properly maintained.

Additionally fixes a scope issue with the booking loop variable.

Test plan:
1. Create a booking with specific dates
2. Edit the booking - verify the item and itemtype are correctly selected
3. Change selections in the modal - verify disabled dates remain correct
4. Create new bookings - verify date restrictions work properly

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:02 -07:00
Martin Renvoize acc2985e6a
Bug 39916: Cypress tests for the bookings modal
This patch adds comprehensive Cypress end-to-end tests for the booking modal,
covering all critical functionality to prevent regressions.

Test coverage includes:
- Basic modal functionality (bookingsModalBasic_spec.ts):
  * Modal loading and initial state
  * Progressive field enabling based on user selections
  * Item type and item dependencies
  * Form validation
  * Booking submission (create and update)
  * Form interactions and field visibility
  * Edit mode functionality
  * Error handling

- Date picker functionality (bookingsModalDatePicker_spec.ts):
  * Flatpickr initialization with future-date constraints
  * Date disabling for existing bookings
  * Date range validation
  * Circulation rules date calculations and visual feedback
  * Lead and trail period functionality
  * Event dots for dates with existing bookings

These tests ensure the booking modal works correctly across all scenarios and
helps maintain code quality during future development.

Test plan:
1. Run the tests inside KTD container:
   docker exec --user kohadev-koha --workdir /kohadevbox/koha -i kohadev-koha-1 \
     bash -c 'npx cypress run --spec "t/cypress/integration/Circulation/bookings*.ts"'
2. Verify all 15 tests pass (9 basic + 6 datepicker)
3. Confirm test coverage is comprehensive for the booking modal

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:01 -07:00
Martin Renvoize 7c4f5eeb56
Bug 39916: Add flatpickr helpers to cypress
This adds helper methods for cypress to work with flatpickr date pickers:
- openFlatpickr() - open the calendar
- selectFlatpickrDate() - select a single date
- selectFlatpickrDateRange() - select date range
- clearFlatpickr() - clear the selected dates
- getFlatpickrDate() - get specific date element
- getFlapickrSelectedDates() - get flatpickr selected dates array
- navigateToFlatpickrMonth() - navigate calendar

These helpers simplify date picker testing when using flatpickr.

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:01 -07:00
Martin Renvoize de4badb166
Bug 39916: Add select2 helpers to cypress
This adds helper methods for cypress to work with select2 dropdowns:
- selectFromSelect2ByIndex() - select by option index
- selectFromSelect2() - select by text value
- clearSelect2() - clear selection

These helpers handle the complexities of interacting with Select2
components which cannot be selected using standard Cypress commands.

Signed-off-by: Kristi Krueger <kkrueger@cuyahogalibrary.org>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:24:00 -07:00
Jonathan Druart 72b06d3576
Bug 41561: Remove 'tab' from admin/aqbudgetperiods
commit 4b312b141a
CommitDate: Wed Sep 19 16:17:16 2012 +0200
    Bug 8117: Divide budget periods into two tabs

It was used at the time
+$url = "aqbudgetperiods.pl?tab=2";

But this has been removed by
commit 870104ce11
Date:   Mon Mar 21 09:32:39 2016 -0400
     Bug 13464 - Standardize the pagination class

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-01-28 14:24:00 -07:00
Jonathan Druart 8b532fc8f4
Bug 41560: Remove id="js" from script tags
Both files
 koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/issuehistory.tt
 koha-tmpl/intranet-tmpl/prog/en/modules/members/readingrec.tt

have
  <script id="js">

That's not needed, useless, and confusing.

Added from those commits for no obvious reasons
catalogue/issuehistory.tt
commit df98c8aaaa
CommitDate: Mon May 14 15:39:44 2012 +0200
    bug_7647: Table sorter on issue history

members/readingrec.tt
commit 77b6409802
CommitDate: Mon Mar 21 20:02:15 2011 +1300
    Bug 5917 : Converted templates

Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:23:59 -07:00
Hammat Wele baff2098bd
Bug 40822: Custom cover images not displayed in search results
When using OPACCustomCoverImages, the custom cover image shows
correctly on the bibliographic detail page but does not appear in the
search results list.

To Test:

1. Disable all cover image preferences (OPACLocalCoverImages,
   OPACAmazonCoverImages, SyndeticsCoverImages, GoogleJackets
   OpenLibraryCovers, BakerTaylorEnabled, OPACCoce).
2. Enable OPACCustomCoverImages.
3. Set CustomCoverImagesURL to {956}.
4. Ensure field 956 is visible in the record editor.
    1. Go to Administration --> MARC bibliographic framework.
    2. Click Actions --> MARC Structure for the default framework.
    3. Search for field 956.
    4. Click Actions --> Edit subfields.
    5. Open the u subfield tab.
    6. Make sure the subfield is in tab 9.
    7. Check OPAC, Intranet, and Editor.
    8. Click Save changes.
5. In a bibliographic record, add the following URL to 956:
    https://m.media-amazon.com/images/I/71Uo7Fqbu3L.jpg
6. At the OPAC, perform a search to display the record in a results
   list.
    --> The custom cover image does not appear in the search results
        list.
7. Apply the patch
8. Repeat step 6
    --> The custom cover image appears in the search results.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:23:59 -07:00
Andrew Fuerste Henry b3bfd82c76
Bug 41163: Do not decode branchcode in circ log
To test:
1 - confirm circ logging is on
2 - check out an item at Centerville (CPL)
3 - go to the log viewer, find your checkout, confirm the Info column includes "CPL"
4 - switch your logged-in library to Fairfield (FFL)
5 - confirm the log viewer now shows "FFL" in the info column for your checkout
6 - use a report to view action_logs.info for your checkout, confirm the field contains "CPL"
7 - apply patch, restart_all
8 - repeat steps 3-5, confirm "info" column shows "CPL" regardless of logged-in branch

Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 14:23:51 -07:00
Lucas Gass faabc711fa
Bug 41682: (RM follow-up) Fix spelling mistake
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-28 07:32:02 -07:00
Jonathan Druart 1b3fe83d6a
Bug 41682: More fixes
* free => Free
* options=NULL where type=Free
* fix options and type for ArticleRequestsSupportedFormats
* fix more explanation and options

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-27 13:56:19 -07:00
Jonathan Druart f3525b169f
Bug 41682: Ensure that sysprefs.sql is kept ordered by syspref's name
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-27 13:56:18 -07:00
Jonathan Druart fecaedbbd8
Bug 41682: Ignore 'FrameworksLoaded'
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-27 13:56:11 -07:00
Fridolin Somers 1534574257
Bug 39745: Fix wrong system preference 'language' in test suite (mock_preference)
Bug 27490 renamed system preference language to StaffInterfaceLanguages.
Needs to be done in test suite on mock_preference calls

Test by running impacted tests

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-26 14:36:44 -07:00
Jonathan Druart 5f9ec5e5c0
Bug 41710: Correctly run tests SearchEngine/Elasticsearch/Search.t in a txn
Test plan:
0. Apply this patch and startup ktd with elastic (e.g `--search-engine es7`)
1. prove t/db_dependent/check_sysprefs.t
=> pass
2. DB cli> SELECT COUNT(*) FROM systempreferences;
3. prove t/db_dependent/Koha/SearchEngine/Elasticsearch/Search.t
4. DB cli> SELECT COUNT(*) FROM systempreferences;
=> No change, still the same number of sysprefs
5. prove t/db_dependent/check_sysprefs.t
=> pass
6. Revert this patch or checkout main
7. prove t/db_dependent/Koha/SearchEngine/Elasticsearch/Search.t
8. DB cli> SELECT COUNT(*) FROM systempreferences;
=> One syspref crated (ElasticsearchIndexStatus_mydb)
9. prove t/db_dependent/check_sysprefs.t
=> fail

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-26 14:36:43 -07:00
Jonathan Druart de3a33a563
Bug 38207: Add VENDOR_PAYMENT_METHOD for new installs
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-26 14:36:43 -07:00
Lucas Gass 84227f70db
Bug 41682: DBRev 25.12.00.014
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 11:07:54 -07:00
Tomás Cohen Arazi c435b04a93
Bug 41682: Fix 'cancelation' typo
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 11:07:53 -07:00
Tomás Cohen Arazi 0ba2c8350c
Bug 41682: Fix 'integer' vs. 'Integer' inconsistency
There are 72 occurences for the 'Integer' type, and 8 for 'integer'.
Tests fail because of `StoreLastBorrower` already, so fixing them all for good.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 11:07:53 -07:00
Tomás Cohen Arazi 8552f04486
Bug 41682: Add ApiKeyLog description
This particular case already introduced discrepancies. Fixing.

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 11:07:52 -07:00
Jonathan Druart 409c804bc1
Bug 41682: Sync DB and sysprefs.sql for new and upgraded installs
This patch removes the discrepancies and put in sync the
systempreferences table for new and upgraded installations.

Main changes:
* Set options to NULL when options=""
* Fix explanation when different
* Fix wrong order (some rows had options=explanation)
* Fix wrong type "Yes/No" => "YesNo"
* Remove StaffLoginBranchBasedOnIP: both StaffLoginLibraryBasedOnIP and
  StaffLoginBranchBasedOnIP are in the DB for upgraded installs
* Remove the 'showsql' from the test. If we need it back then it must be
  a maintenance script.
* Improve the tests:
  * Compare sysprefs.sql and the DB content for options, explanation and type
  * Catch type not defined
  * Catch incorrect YesNo values (must be 0 or 1)

Later:
* Add a C4::Installer sub to add new syspref:
  add_pref(
      {
          variable    => $variable,
          value       => $value,
          options     => $options,
          explanation => $explanation,
          type        => $type
      }
  );
* Clean 'type' and make it an ENUM
* Make it easy to run the test for devs (should be in xt but we need it
  in t/db_dependent for packages - FIXME?)
* Run the tests on upgraded installs on CI

Test plan:
0. Apply the "tests" patch only
1. prove it
=> It fails loudly
2. Apply the DB change and run updatedatabase, prove the tests
=> they pass
3. Test upgraded installs:
  * checkout v24.05.00 (the version we use for Jenkins'
    Koha_Main_MariaDB_update job)
  * reset_all
  * checkout the branch with this patchset
  * updatabase
  * prove t/db_dependent/check_sysprefs.t
  => Should be green

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 11:07:52 -07:00
Jonathan Druart 42c2fc7dbf
Bug 41682: Add tests
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Edit: uncommented the `Test::NoWarnings` line, probably commented by mistake.
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 11:07:44 -07:00
Connor Cameron-Jones b218bbc8c0
Bug 41347: Terminology: Item had a reserve waiting
"Item had a reserve waiting" changed to "Hold waiting on item"
"Item was reserved" changed to "hold placed on item"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 07:29:04 -07:00
Jonathan Druart ac1d6a7f2c
Bug 41557: Remove unused vars sent to template
488         LoginFirstname               => ( C4::Context->userenv ? C4::Context->userenv->{"firstname"} : "Bel" ),
 489         LoginSurname                 => C4::Context->userenv ? C4::Context->userenv->{"surname"}      : "Inconnu",
 490         emailaddress                 => C4::Context->userenv ? C4::Context->userenv->{"emailaddress"} : undef,

Test plan:
`git grep` the 3 variables and confirm that there is no occurrences in
the templates (.tt/.inc files)

Signed-off-by: Connor Cameron-Jones <22264cc@hvhs.school.nz>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 07:29:03 -07:00
David Nind b4cbe3c6fe
Bug 32285: (QA follow-up) Remove additional spaces before colons 325$j
This removes two additional spaces before colons for the UNIMARC 325$j
value builder (unimarc_field_325j.pl plugin).

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 07:29:03 -07:00
Photonyx 7bf6f009a6
Bug 32285: Removed spaces before the colons
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 07:29:02 -07:00
Raguram Gopinath 8a09573fa3
Bug 40031: Redirect to add action page from add template form
Test plan:
1. Login to staff client.
2. Go to: Cataloging > MARC modification templates > New template
3. Give new template a name and click submit.
4. Following URL loads: http://localhost:8081/cgi-bin/koha/tools/marc_modification_templates.pl
If you are using KTD
5. Apply patch and restart services.
6. Repeat steps 2 and 3.
7. Following URL loads: http://localhost:8081/cgi-bin/koha/tools/marc_modification_templates.pl?template_id=(template_id)&op=select_template
8. You can now add actions for your new template.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 07:29:02 -07:00
Lari Taskula 3c34d7bf43
Bug 35423: Fix substr outside of string warning
To test:

Before applying patch

1. Create an authority record, fill field 400$w with "n"
2. Search for the authority you just created
3. Observe a warning has been logged

"substr outside of string"

4. Apply patch
5. Refresh the search results from step 2
6. Observe no warnings have been logged

Signed-off-by: Harrison Hawkins <harrisonhawkins@catalyst.net.nz>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 07:29:01 -07:00
Owen Leonard 1e84cbd496
Bug 39715: (follow-up) Undo change to shelves_results.tt
This must have been an overzealous find and replace. A similar template,
itemsearch_json.tt, was not similarly affected.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 07:29:01 -07:00
Owen Leonard 684c014577
Bug 39715: Do not quote DataTables options
This patch updates templates so that the options passed to DataTables
(via kohaTable) are not quoted. The quotes are not necessary, and
are not consistent with official DataTables documentation. This
establishes a standard for us to follow in the future.

The patch also corrects some option names which required upates,
particularly "sorting" -> "order".

To test, apply the patch and test updated DataTables.

Sponsored-by: Athens County Public Libraries

Rebased-by: Jonathan Druart
paginate => paging

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-23 07:28:54 -07:00
Lucas Gass 166604f9f3
Bug 41539: (follow-up) Add missing filters
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 15:27:28 -07:00
Nick Clemens f9912bcd1d
Bug 41539: (QA follow-up) Add classes for styling
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 15:27:28 -07:00
Andrew Fuerste Henry 8a7109be70
Bug 41539: Add item barcode to hold waiting message
To test:
1 - have or create a waiting hold
2 - go to the patron's record, see your waiting hold is listed in the patron messages but does not include barcode
3 - apply patch, reload page
4 - confirm item barcode appears on both Check Out and Details pages, for holds waiting both at your current branch and at another branch

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 15:27:27 -07:00
Owen Leonard 6efd0b04e0
Bug 41497: ul.patronbriefinfo inconsistent in coding structure
This patch updates the markup of the include which displays patron
information on the checkout and patron detail pages. The changes are
minor and should have no visible effect.

To test, apply the patch and search for a patron in the staff interface.

View the patron information in the sidebar on both the checkout and
patron detail pages. Everything should look correct.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Manvi <manvisharma1006@gmail.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 15:21:21 -07:00
Mercury WallacE db11834480
Bug 41398: Fixed Typo of Tagret item is not in the local hold group
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-01-22 15:21:20 -07:00
Mia 02b752e25c
Bug 41397: Changed "not reservable" to "cannot be placed on hold"
test plan -
step 1 - apply patch
step 2 - observe in koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt:221 that Target item is not reservable is now cannot be placed on hold

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 15:21:20 -07:00
Photonyx df82b31ccb
Bug 41658: "Data provider" string fix
Fixed the miscapitalisation - "Data Provider" should be "Data provider" according to our spelling rules

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 15:21:19 -07:00
Photonyx 23dfbd051f
Bug 41351: Capitalisation fix
Corrected and standardized the capitalization of "Override Renew hold
for another" to "Override renew hold for another"

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-01-22 15:21:12 -07:00
Samuel Young ccf41d8b21
Bug 41540: Updated description for system preferences for staffShibOnly
Test Plan:
1. Apply Patch, go to the staff client
2. Navigate to the Administration
3. Search for staffShibOnly within System Preferences
4. Check that the description properly displays the right information
   "staff to log in by means other than Shibboleth"

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-01-22 14:16:06 -07:00
Lucas Gass 34bb9ad633
Bug 26993: DBIC changes
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:06 -07:00
Lucas Gass 629838dea2
Bug 26993: (follow-up) Update kohastructure.sql
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:05 -07:00
Lucas Gass 833f370fd5
Bug 26993: DBRev 25.12.00.013
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:05 -07:00
Lucas Gass edc7b29b17
Bug 26993: Use last_returned_by->delete_all
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:04 -07:00
Lucas Gass 9d4b4efffd
Bug 26993: (follow-up) Rename last_returned_by_all as last_borrowers
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:04 -07:00
Lucas Gass cf018a0bf4
Bug 26993: (follow-up) Return Koha::Patron resultset from last_returned_by_all
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:03 -07:00
Jonathan Druart c05bc3ad7e
Bug 26993: Use a txn
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:03 -07:00
Jonathan Druart aee88e8672
Bug 26993: Use FOREIGN_KEY_CHECKS
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:02 -07:00
Lucas Gass e25ecf6757
Bug 26993: last_returned_by_all should return an array ref
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:02 -07:00
Lucas Gass c0f1b747c4
Bug 26993: Unit tests
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:01 -07:00
Lucas Gass 4b04138ed1
Bug 26993: Implement changes
Test plan:

1. APPLY patch, restart_all, updatedatabase
2. Search for the StoreLastBorrower system preference, it should now allow you to enter a number of borrowers to store
3. Set the pref to 3.
4. Check an item out to any patron. Then immediately check it back in.
5. On catalogue/moredetail.pl?biblionumber=X ( where X is you biblionumber ) you should see a "Last returned by:" entry with the correct cardnumber for the borrower.
7. Confirm this is right in the database with: SELECT * FROM items_last_borrower where itemnumber = X; ( Where X is your itemnumber )
8. Repeat step 4 to a different borrower.
9. Repeat steps 5 and 6. You should see 2 cardnumbers of each of the borrowers on catalogue/moredetail.pl?biblionumber=X
10. Repeat step 4 with another new borrower.
11. Repeat steps 5 and 6. You should see 3 cardnumbers of each of the borrowers on catalogue/moredetail.pl?biblionumber=X
12. Choose a different item and repeat steps 4 - 11 for this item.
13. Now set the StoreLastBorrower preference to 0.
14. Using the original item from step 4, check something out to a patron and immediately check it back in.
15. catalogue/moredetail.pl?biblionumber=X should NOT have an entry for "Last returned by:"
16. Check the database, all entries for the itemnumber from step 14 should be deleted.

Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:01 -07:00
Lucas Gass ca47f953dd
Bug 26993: System preference updates
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:00 -07:00
Lucas Gass 19ef8940f9
Bug 26993: Schema changes
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:16:00 -07:00
Lucas Gass 0fc32b7de8
Bug 26993: Database updates
Signed-off-by: Trevor Diamond <trevor.diamond@mainlib.org>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:15:59 -07:00
Lari Taskula 88e07a074e
Bug 41000: Relabel 041$d "Spoken language" to "Sung or spoken language"
Updates the XSLT templates for detail pages in the staff interface
and OPAC to label 041$d in MARC21 as "Sung or spoken language",
matching the MARC21 definition.

For music libraries, this also better reflects the information
about the record.

To test:
1. Apply patch
2. Catalog a record with 041$d
3. Navigate to the record details both in the staff interface
   and OPAC
4. Observe "Sung or spoken language: <041$d>"

Signed-off-by: Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 14:15:52 -07:00
Lucas Gass 011f38553f
Bug 40192: (follow-up) Adjust api/v1/extended_attribute_types.t test
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 13:27:31 -07:00
Marcel de Rooy b913df0844
Bug 41060: (QA follow-up) Consistent return for empty string
An empty ISBN should not warn just like an empty ISSN.

Test plan:
Verify that clearing ISBN or ISSN does not warn anymore.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:32 -07:00
Marcel de Rooy 76a1dc2dd6
Bug 41060: (QA follow-up) Check in change, trigger once in Blur
If we already checked and nothing changes, no need to check again.
Set focus to field if we warn.
For consistency copied these changes to validate_isbn.

Test plan:
Try various valid and invalid ISSNs.
Tab through the field repeatedly; you should only have a warning
once, if the ISSN is invalid (yellow colored).
Save a bad ISSN into a record. Open it again and tab through ISSN;
you should be warned.

Repeat similarly for ISBN field.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:32 -07:00
Arthur Suzuki c3e8b34e6a
Bug 41060: (QA follow-up) improve ISSN format detection
Two tests have been added:
- not only the checksum of the ISSN is checked but also the format.
- if the ISSN is left empty, the plugin won't give a warning.

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-01-22 10:02:31 -07:00
Arthur Suzuki 110f7d8030
Bug 41060: Add a cataloguing plugin to check ISSN
Test plan:
- without patch login to Koha
- edit a biblio and put a wrong ISSN in 022a (011a for unimarc).
- leave the input field (looses focus), no warning
- apply patch and do "reset_all" to make a full re-install.
- edit the biblio again and put an invalid ISSN in 022a (011a for unimarc).
- when you leave the input field you should have a warning saying
  "invalid ISSN".
- the input field should turn yellow
- you can still save the biblio with a wrong issn, it is just a warning.
- if you apply the patch on an existing install, you must manually link
  the value builder to your existing frameworks to get it working.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
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-01-22 10:02:31 -07:00
David Cook b86095ffe7
Bug 40719: Explicitly turn off RELATIVE for TemplateUtils
This change explicitly turns off RELATIVE in Koha/TemplateUtils.pm

Test plan (Koha/TemplateUtils.pm):
0. Apply the patch and koha-plack --restart kohadev
1. Go to http://localhost:8081/cgi-bin/koha/tools/letter.pl
2. Click "New notice" and choose "reports"
3. Add the following to the report:
[% IF ( x == 1 ) %]
One
[% ELSE %]
Two
[% END %]
4. Go to http://localhost:8081/cgi-bin/koha/reports/guided_reports.pl?phase=Use%20saved
5. Create a report like the following:
SELECT biblionumber FROM biblio;
6. Click "Run with template" and choose the Reports template you made
7. Note that the output shows "Two"
8. Edit your Reports notice and include the following:
[% INSERT ../etc/passwd %]
9. Re-run the report using "Run with template"
10. Note that the output shows the regular SQL results (ie template transform not successful)

Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:30 -07:00
Kyle M Hall bd85f990da
Bug 40719: Explicitly disable off RELATIVE file paths for plugins for user-entered templates
In bug 39184 we set EVAL_PERL and ABSOLUTE to 0, which is the default. RELATIVE also defaults to 0. For security we should probably specify that as well in case the default is changed somehow.

Test Plan:
1) Apply this patch
2) Generate some notices
3) No change should be noted!

Signed-off-by: noah <noah@inlibro.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:30 -07:00
Nick Clemens 31d52435b6
Bug 37099: (QA follow-up) Combine fields in mappings for brevity
The syntax '630anr' is equivalent to mapping each individual field, this just keeps the file smaller

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:29 -07:00
Wainui Witika-Park 522024f2df
Bug 37099: Add uniform title fields to the subject Elasticsearch index mappings
Add 'subject' or 'subject as phrase' search to also search on the following Uniform Title fields: 630$a

Sponsored-by: Educational Services Australia SCIS
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:29 -07:00
Lucas Gass 38c15330f4
Bug 39418: DBRev 25.12.00.012
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:28 -07:00
phette23 26a17774e7
Bug 39418: RDA Carrier, Content, and Media Vocabularies database update
This contains a database update script so that existing Koha instances
also receive authorised values for the RDA vocabularies.

Sponsored-by: California College of the Arts
Signed-off-by: Miranda Nero <mnero@oslri.net>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:28 -07:00
phette23 2426dc4959
Bug 39418: RDA Carrier, Content, and Media Vocabularies
This adds authorized value lists for use in MARC21 tags 336 - Content Type,
337 - Media Type, and 338 - Carrier Type, subfields $a and $b.

Test plan:
1. Create a fresh Koha instance
2. Navigate to admin > authorized values
3. Note there are no RDA value categories
4. Apply the patch and recreate a new Koha
5. Repeat step 2
6. There are six RDA value categories: RDACARRIER, RDACARRIER_CODE,
RDACONTENT, RDACONTENT_CODE, RDAMEDIA, and RDAMEDIA_CODE

Sponsored-by: California College of the Arts
Signed-off-by: Miranda Nero <mnero@oslri.net>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:27 -07:00
Pedro Amorim db22aad838
Bug 41009: Return to 'Manage request' page after editing
Test plan (before applying patch):
1) Enable ILLModule
2) Create a new ILL request, visit:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
3) After creating the request, you're on the 'Manage request' page.
4) Click 'Edit request'. Click 'Submit'. Notice you're redirected to the list table.
5) Apply patch. Repeat. Notice you're sent to the ILL request 'Manage request' page.

Signed-off-by: Richard Bridgen <richard.bridgen@nhs.net>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:27 -07:00
Kyle M Hall 356903a96a
Bug 32370: (QA follow-up) Add https to license url, use Test::NoWarnings
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:26 -07:00
Tomas Cohen Arazi 71fce974e3
Bug 32370: Adapt Koha::Auth::Identity::Provider
This patch adapts the Koha::Auth::Identity::Provider class to
make use of this, and also to prove the behavior is correct (expected
results, exceptions, encoding, etc).

To test:
1. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Auth/Identity/Provider.t \
           t/db_dependent/api/v1/idp.t
=> SUCCESS: Identity provider related tests pass!
2. Apply this patches
3. Repeat 1
=> SUCCESS: Tests still pass!
4. Sign off :-D

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:26 -07:00
Tomas Cohen Arazi 6d4965f2d3
Bug 32370: Provide a generic set of tools for JSON fields
This patch introduces a new class for extending Koha::Object-based
classes so they have reusable ways to deal with JSON encoded fields.

As documented in the POD, classes can now

```perl
use base qw(Koha::Object Koha::Object::JSONFields);
```

and will have the following methods available:

* decode_json_field
* set_encoded_json_field

For convenience, the Koha::Auth::Identity::Provider class is tweaked to
make use of this, and also to prove the behavior is correct (expected
results, exceptions, encoding, etc).

To test:
1. Apply this patch
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/Object/JSONField.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:25 -07:00
Tomas Cohen Arazi d919f77118
Bug 32370: Unit tests
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:25 -07:00
Lucas Gass ffa5704c95
Bug 40435: DBRev 25.12.00.011
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:24 -07:00
Martin Renvoize cd47fb5ca7
Bug 40435: (follow-up) Change preference type to YesNo
Change FutureHoldsBlockRenewals from type 'Integer' to 'YesNo'
for consistency with other boolean preferences in Koha (e.g.,
AllowHoldDateInFuture, OPACAllowHoldDateInFuture).

While the preference functionally works as Integer due to the
.pref file defining the choices, YesNo is the established
convention for binary system preferences.

Test plan:
1. Apply this patch
2. Run the database update
3. Verify the preference appears correctly in system preferences
4. Verify the preference still functions properly with tests:
   prove t/db_dependent/Circulation.t :: "CanBookBeRenewed | future holds"

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:24 -07:00
Martin Renvoize 70ced21a4d
Bug 40435: (follow-up) Fix DateTime mutation in test
The test had a bug where $future_date was being mutated by the
add() method on line 1975, causing line 1982 to use 4 days
instead of 2 days. This meant the test wasn't properly verifying
that FutureHoldsBlockRenewals=0 prevents blocking for holds
within the ConfirmFutureHolds window.

Test plan:
1. Run: prove t/db_dependent/Circulation.t :: "CanBookBeRenewed | future holds"
2. Verify all tests pass
3. Confirm the test now properly validates the intended behavior

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:23 -07:00
Marcel de Rooy 1c7aab82c5
Bug 40435: Add tests
Test plan:
Run t/db_dependent/Circulation.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:23 -07:00
Marcel de Rooy 71b439d855
Bug 40435: Module changes
CanBookBeRenewed will by default pass the skip_future_holds flag
to item->current_holds so that renewals are not blocked by future
holds.
The new pref FutureHoldsBlockRenewals allows you to change that
historical behavior.

Test plan:
[1] Set pref ConfirmFutureHolds=3, FutureHoldsBlockRenewals=Allow,
    OPACAllowHoldDateInFuture=Allow
[2] Checkout some item to patron A.
[3] Place hold on same item for patron B, reserve date tomorrow.
[4] Try to renew item with patron A on account page. Should be
    marked as Not renewable.
[5] Set pref FutureHoldsBlockRenewals=Dont
[6] Repeat step 4. Renew should be allowed.
[7] prove t/db_dependent/Holds.t t/db_dependent/Reserves.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:22 -07:00
Marcel de Rooy 87c143b4c2
Bug 40435: Add to HEA
Grouping all future hold prefs here too.

Test plan:
prove t/db_dependent/UsageStats.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:22 -07:00
Marcel de Rooy 5b5ffc57c7
Bug 40435: New preference FutureHoldsBlockRenewals
Test plan:
Change this pref value under Administration.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:21 -07:00
Marcel de Rooy f3978e5b1c
Bug 40435: Database revision
Test plan:
[1] Run install or upgrade (updatedatabase.pl). Verify that pref was added to database.
* perl -MC4::Context -e'print C4::Context->preference('FutureHoldsBlockRenewals'),"\n"'
* You should see a zero printed.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:21 -07:00
Lucas Gass 9f9fe699f3
Bug 38061: DBRev 25.12.00.010
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:20 -07:00
Victor Grousset/tuxayo 00988f4d94
Bug 38061: (QA follow-up): dbrev: print if column already exists
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:20 -07:00
Matt Blenkinsop 0a669a7e97
Bug 38061: (QA follow-up) Add default falsy value for subscription creation/editing
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:19 -07:00
Matt Blenkinsop f5ede57411
Bug 38061: Add a checkbox to determine whether collection table issues should be pre-selected
Test plan:
Apply the patches and update the database
1) Create a new subscription with items, there should be a checkbox at the bottom of the form to pre-select collection table issues - make sure this is checked
2) Receive some items against the subscription
3) Navigate to the collections table
4) The checkboxes should be pre-selected as per previous behaviour in Koha
5) Edit the subscription and uncheck the checkbox
6) Return to the collections table and the checkboxes should all be unchecked

Signed-off-by: Nathalie <nathalie.savarit@univ-amu.fr>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:19 -07:00
Matt Blenkinsop 2894e34c1e
Bug 38061: Add item column to serial collections table
This patch adds a column to the table to link to the item

Test plan:
1) Add a new subscription to a serial record (with the option 'create an item record' selected)
2) Receive several issues and create the linked items
3) Navigate to the serials collection table
4) There should be a column for the linked item
5) Click on one of the links, it should display the item

Signed-off-by: Nathalie <nathalie.savarit@univ-amu.fr>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 10:02:11 -07:00
Owen Leonard d5de3ee6cd
Bug 38643: (QA follow-up) Replace _() with __() in global.js
The double-underscore function is required for translations to be picked
up in JS files.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:11 -07:00
Laura_Escamilla 7c5c8e74bc
Bug 38643: Added JS for the dynamic placeholders
To test:

1. Visit http://localhost:8080/cgi-bin/koha/opac-main.pl — notice that
   the main search bar has a placeholder.
2. Visit the advanced search page. Notice that the search bars in the
   advanced search do not have placeholders.
3. Apply this patch. restart_all
4. Refresh the page on the advanced search. Notice that the search bars
   now have placeholders. Try switching out the dropdown options and
   notice that the placeholder text changes depending on what you
   select.
5. Test searches and make sure everything works as expected.
6. Sign off — have an amazing day! :D

Signed-off-by: Ludovic <ludovic.julien@inlibro.com>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:11 -07:00
Laura_Escamilla 2ad6b96983
Bug 38643: Added dynamic placeholders to the advanced search inputs
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:10 -07:00
Laura_Escamilla b28daeb973
Bug 40794: Add id='account-tabs' wrapper to payment and account tab templates
This patch adds a new id="account-tabs" to that div.

To test:

Apply the patch and restart_all. Clear browser cache if necessary.

1. Go to a patron’s accounting tab → Transactions / Make payment / Manual invoice / Manual credit.
2. Inspect the HTML for the tabs menu. The surrounding div should have both class="toptabs" and id="account-tabs".
3. Verify styling and tab functionality remain unchanged.
4. Sign off and have a great day! :D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:10 -07:00
Jonathan Druart 9f947a2517
Bug 41047: Sort libraries by description instead of code
On the holdings tables the libraries are sorted by code, we should sort
them by description.

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:09 -07:00
Pedro Amorim 32ea6abb1e
Bug 41054: Sort metadata keys alphabetically case insensitive
This coincidentally+nicely puts eISSN before ISSN on the 'manage request page' while keeping the rest of the order as before

Test plan:
1) Enable ILLModule
2) Create a new ILL request, visit:
<staff_url>/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=Standard
3) Pick type one of 'journal article', 'conference', 'journal', 'generic resource'
4) Enter cardnumber, library, eISSN and other data if you want
5) Notice 'eISSN' shows on the 'Manage request' page.
6) Test again by creating an OPAC request, visit:
<opac_url>/cgi-bin/koha/opac-illrequests.pl?op=add_form&backend=Standard

Signed-off-by: Richard Bridgen <Richard.Bridgen@nhs.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:09 -07:00
Pedro Amorim d8989fe57f
Bug 41054: Add eISSN to Standard forms
Signed-off-by: Richard Bridgen <Richard.Bridgen@nhs.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:08 -07:00
Pedro Amorim e6788ab699
Bug 41054: Add test
prove t/db_dependent/Koha/ILL/Backend/Standard.t

Signed-off-by: Richard Bridgen <Richard.Bridgen@nhs.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:08 -07:00
David Cook 58f0439bcf
Bug 41646: Fix HTML display for self-checkin
This change moves the wrapper so that the web page display correctly

Test plan:
0. Apply the patch
1. Enable the SelfCheckInModule system preference
2. Go to http://localhost:8080/cgi-bin/koha/sci/sci-main.pl
3. Note that there isn't a lot of whitespace between
the word "koha" and "Self check-in"
4. Celebrate

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:07 -07:00
Tomás Cohen Arazi 8f7a1b861f
Bug 41603: Move after_hold_action plugin hook outside transaction
The after_hold_action plugin hook is currently called from within the
hold cancellation transaction in Koha::Hold->cancel(). This creates
a fragile architecture where any plugin performing database queries
can cause lock timeouts and transaction failures.

The issue manifests when plugins query tables with foreign key
relationships to biblio records. Even lightweight queries on seemingly
unrelated tables (like illrequests) can trigger lock conflicts due to
foreign key constraints that require shared locks on referenced rows.

Since the plugin hook executes while the transaction holds locks on
reserves/biblio tables, any plugin database access creates potential
for circular lock dependencies and timeouts.

This patch moves the plugin hook call outside the transaction boundary,
ensuring that:
- Core hold operations complete without plugin interference
- Plugins receive the same data but execute after transaction commit
- Database lock conflicts are eliminated
- Plugin failures cannot corrupt core hold cancellation logic

The hook receives identical data through a stored reference to the
cancelled hold, maintaining full backward compatibility while
eliminating the transaction fragility.

Test plan:
1. Install any plugin that implements after_hold_action hook with
   database queries (e.g., ILL plugins checking hold relationships)
2. Create a hold and attempt to cancel it
3. Without patch: Operation may timeout with "Lock wait timeout exceeded"
4. With patch: Operation completes immediately
5. Verify plugin hook still receives correct hold data

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:07 -07:00
Lucas Gass b3cea14d28
Bug 38207: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:06 -07:00
Lucas Gass e8d42b7abc
Bug 38207: DBRev 25.12.00.009
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:06 -07:00
Andrew Fuerste Henry dcf1a2b959
Bug 38207: Add exec flag to atomicupdate
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:05 -07:00
Matt Blenkinsop d221187a41
Bug 38207: Update cypress tests
(cherry picked from commit 61bcde708a970d1bca07e4a803ae5dfe78c16c89)
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:05 -07:00
Matt Blenkinsop 23af641f5a
Bug 38207: Update UI to use new field
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:04 -07:00
Matt Blenkinsop 2234969a83
Bug 38207: Add a new payment_method column to aqbooksellers
(cherry picked from commit 881f96172a8c1b35d3e74f7f0f6aa44629578021)
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:04 -07:00
Lucas Gass 12b73f4719
Bug 20638: DBRev 25.12.00.008
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:03 -07:00
Tomás Cohen Arazi 95ce864450
Bug 20638: (follow-up) Silence viewlog.pl warning
This patch fixes useless warnings being printed by `viewlog.pl` I
noticed while testing this new feature.

To test:
1. Test the feature introduced by this patchset
2. Make sure you are running:
   $ ktd --shell
  k$ tail -f /var/log/koha/kohadev/*.log
=> FAIL: You see things like:

```
[2025/09/26 18:37:39] [WARN] Use of uninitialized value in substr at /kohadevbox/koha/tools/viewlog.pl line 156.
[2025/09/26 18:37:39] [WARN] Use of uninitialized value in substr at /kohadevbox/koha/tools/viewlog.pl line 156.
[2025/09/26 18:37:39] [WARN] Use of uninitialized value in substr at /kohadevbox/koha/tools/viewlog.pl line 156.
[2025/09/26 18:37:39] [WARN] Use of uninitialized value in substr at /kohadevbox/koha/tools/viewlog.pl line 156.
[2025/09/26 18:37:39] [WARN] Use of uninitialized value in substr at /kohadevbox/koha/tools/viewlog.pl line 156.
[2025/09/26 18:43:44] [WARN] Use of uninitialized value in substr at /kohadevbox/koha/tools/viewlog.pl line 156.
[2025/09/26 18:43:44] [WARN] Use of uninitialized value in substr at /kohadevbox/koha/tools/viewlog.pl line 156.
```
3. Refresh the viewlog page
=> FAIL: They are consistent
4. Apply this patch
5. Repeat 3
=> SUCCESS: No more warnings!
6. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:03 -07:00
Tomás Cohen Arazi 130ee49ba4
Bug 20638: Improve ApiKey logging to use structured data
This patch enhances the ApiKey logging mechanism to pass structured
object data instead of formatted strings to logaction(), following
the ACTN1 coding guideline for action logs.

Changes:
- Pass the ApiKey object directly to logaction() for CREATE/REVOKE/ACTIVATE
- Pass undef for info and the object in diff field for DELETE operations
- Refactor store/revoke/activate methods to improve logging consistency
- Update tests to handle the new structured logging format

The new approach provides richer, more structured log data that can be
better utilized by log analysis tools and maintains consistency with
other Koha object logging patterns.

Test plan:
1. Enable ApiKeyLog system preference
2. Create, revoke, activate, and delete API keys
3. Verify action_logs table contains structured object data
4. Run:
   $ ktd --shell
  k$ prove t/db_dependent/Koha/ApiKey.t
=> SUCCESS: Tests pass!
5. Sign off :-D

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:02 -07:00
Tomás Cohen Arazi 6720260774
Bug 20638: Include APIKEYS in 'Modification log'
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:02 -07:00
Tomás Cohen Arazi ddb282013f
Bug 20638: Add APIKEYS support to log viewer
Add comprehensive support for viewing API key logs in the log viewer:

tools/viewlog.pl:
- Add APIKEYS to patron lookup logic alongside MEMBERS, CIRCULATION, FINES
- Enable patron name display for API key log entries

koha-tmpl/.../viewlog.tt:
- Add APIKEYS to the modules list for filtering
- Add ACTIVATE and REVOKE to the actions list for filtering
- Include APIKEYS in patron display logic for object column

koha-tmpl/.../action-logs.inc:
- Add APIKEYS module translation: 'API keys'
- Include ApiKeyLog system preference check with warning icon
- Add ACTIVATE and REVOKE action translations
- Maintain alphabetical order in translation blocks

This enables librarians to view and filter API key creation, activation,
revocation, and deletion activities in the standard log viewer interface,
with proper patron name resolution and preference-based logging status
indicators.

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:01 -07:00
Tomás Cohen Arazi bc6110ae10
Bug 20638: Update apikeys.pl and template with proper exception handling
This patch comprehensively updates the API keys management interface
to use the new revoke() and activate() methods with proper exception
handling.

Controller changes (members/apikeys.pl):
- Add Try::Tiny and Scalar::Util imports for exception handling
- Replace direct field manipulation (active(0)/active(1)) with proper
  method calls (revoke()/activate()) to ensure logging
- Wrap method calls in try/catch blocks to handle specific exceptions:
  * Koha::Exceptions::ApiKey::AlreadyRevoked
  * Koha::Exceptions::ApiKey::AlreadyActive
- Implement standard Koha messages pattern with @messages array
- Improve code structure with elsif chain instead of multiple if statements
- Add proper error handling for key_not_found cases
- Include error_string for unhandled exceptions to aid debugging

Template changes (koha-tmpl/.../apikeys.tt):
- Add comprehensive error message handling for all exception types:
  * already_revoked: Clear message for revoked keys
  * already_active: Clear message for active keys
  * key_not_found: Message for missing keys
  * unhandled_exception: Generic message with detailed error string

Key benefits:
- All API key state changes are now properly logged when ApiKeyLog is enabled
- Better user experience with specific, actionable error messages
- Robust exception handling prevents crashes and provides debugging info
- Improved code maintainability and extensibility

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:01 -07:00
Tomás Cohen Arazi be1c64aa46
Bug 20638: Add revoke() and activate() methods to Koha::ApiKey
This patch adds two new methods to Koha::ApiKey:
- revoke(): Sets active to 0, throws exception if already revoked
- activate(): Sets active to 1, throws exception if already active

Both methods include proper exception handling and comprehensive tests.

Test plan:
1. prove t/db_dependent/Koha/ApiKey.t
2. Verify new subtests pass for revoke() and activate() methods
3. Confirm exceptions are thrown for invalid state transitions

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:00 -07:00
Tomás Cohen Arazi ad7f5c1d80
Bug 20638: Add ApiKeyLog system preference
This patch adds the ApiKeyLog system preference to control logging
of API key actions (creation, deletion, revocation, activation).

The preference follows the same pattern as BorrowersLog and other
logging preferences.

Test plan:
1. Apply patch and run database update
2. Go to Administration > System preferences > Logs
3. Verify ApiKeyLog preference appears with Yes/No options
4. Verify default value is 'Don't log' (0)

Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:26:00 -07:00
Martin Renvoize 2025731e52
Bug 38728: (follow-up) Fix cashup modal triggering after POST/REDIRECT/GET implementation
The upstream implementation of POST/REDIRECT/GET pattern for cashup operations
introduced a regression where the cashup modal would no longer auto-trigger
after successful cashup submission. The original approach passed cashup_id as
a query parameter in the redirect, but the template variable was not being
populated from the URL parameter, causing the JavaScript to receive an empty
value.

This follow-up implements a more modern and reliable solution using URL hash
fragments instead of query parameters:

1. Changed redirect URLs to use hash fragments (e.g., #cashup-123)
2. Updated JavaScript to detect and parse hash fragments on page load
3. Auto-triggers the cashup modal when hash is present
4. Cleans up the URL using history.replaceState() after modal opens

Benefits:
- Maintains proper POST/REDIRECT/GET pattern
- Hash fragments don't trigger server requests or require server-side handling
- More reliable than template variable approach
- Follows modern web development patterns
- Works consistently across both register.pl and registers.pl pages

Test plan:
1. Navigate to POS register page (pos/register.pl)
2. Perform a cashup operation
3. Verify the cashup summary modal automatically appears after redirect
4. Verify the URL is clean (no hash fragment visible)
5. Repeat for registers summary page (pos/registers.pl)
6. Perform individual register cashup
7. Verify modal auto-triggers correctly

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:59 -07:00
Martin Renvoize f57279b52c
Bug 38728: Add automatic modal popup for cashups in registers summary
This patch automatically triggers the cashup display modal after a
cashup is performed from the Cash registers summary page when performing
a cashup on an individual register.

Test plan:
1. Enable the point of sale system preference
2. Ensure you have permission for cash_management > cashup
3. Create at least one cash register for your current branch
4. Navigate to More > Point of Sale > Cash registers
5. Perform some transactions to create outstanding amounts
6. Click 'Record cashup' button on an individual register
7. Confirm the cashup in the modal dialog
8. Verify that after page reload, the cashup summary modal
   automatically opens showing the details of the cashup just performed

Signed-off-by: Sally <sally.healey@cheshiresharedservices.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:59 -07:00
Martin Renvoize c122b45af8
Bug 38728: Add automatic modal popup for cashups on register details page
This patch automatically triggers the cashup display modal after a
cashup is performed from the individual Register details display page.

Test plan:
1. Enable the point of sale system preference
2. Ensure you have permission for cash_management > cashup
3. Create at least one cash register for your current branch
4. Navigate to More > Point of Sale > Cash registers
5. Click on a register name to access the individual register page
6. Perform some transactions to create outstanding amounts
7. Click 'Record cashup' button on the register details page
8. Confirm the cashup in the modal dialog
9. Verify that after page reload, the cashup summary modal
   automatically opens showing the details of the cashup just performed

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Sally Healey <sally.healey@cheshiresharedservices.gov.uk>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:58 -07:00
Jonathan Druart 0a3ed65e70
Bug 40192: (bug 38446 follow-up) Add title to /erm/extended_attribute_types
Sponsored-by: Karlsruhe Institute of Technology (KIT)
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:58 -07:00
Matt Blenkinsop 806ac743f0
Bug 40192: Add additional fields support to ERM titles
This patch extends the additional fields framework to ERM e-holdings
titles, allowing libraries to configure custom metadata fields for
managing electronic resources.

Implementation details:
- Extended Koha::ERM::EHoldings::Title and ::Titles with
  AdditionalFields mixin for object-level support
- Added 'erm_titles' table mapping to AdditionalField.pm for API
  serialization
- Updated OpenAPI schema definitions to include extended_attributes
  in erm_eholdings_title.yaml and expose extended_attribute_types
  endpoint
- Enhanced REST API controller to handle additional fields in title
  creation/updates
- Updated admin interface to allow configuring additional fields for
  the 'title' table name
- Modified Vue.js EHoldingsLocalTitleResource component to display
  and manage custom fields

This enables institutions to track custom metadata (e.g., license
details, administrative notes, local identifiers) alongside standard
ERM title information.

Test plan:
1. Configure additional fields:
   - Go to Administration > Additional fields
   - Click "New field" and select table name "title"
   - Create a text field: Name="License notes", Searchable=Yes
   - Create an authorized value field: Name="Access level",
     AV category=LOC (or any available category)
   - Save both fields

2. Test with local ERM titles:
   - Navigate to E-resource management > Local titles
   - Create a new local title or edit an existing one
   - Verify the "Additional fields" section appears with your
     configured fields
   - Fill in "License notes" with test text
   - Select a value for "Access level"
   - Save the title

3. Verify persistence:
   - Reopen the title you just edited
   - Confirm both additional field values are displayed correctly
   - Edit one field value and save
   - Confirm the update persists

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:57 -07:00
Matt Blenkinsop c04ac1c541
Bug 40191: Tidy files without the DO NOT PUSH patch
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:56 -07:00
Matt Blenkinsop ce6367fede
Bug 40191: Handle resources without detail view in navigation options
Fixes an edge case where resources without a dedicated show/detail component
(like Record Sources) would show invalid navigation options.

- ResourceFormSave: Filter out "show" option when no_show is true
- RecordSourcesResource: Set no_show flag to hide inappropriate options

Ensures the split button menu only shows valid navigation targets.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:56 -07:00
Matt Blenkinsop 671ecb0731
Bug 40191: Add Cypress test coverage for split button navigation
Adds E2E test in VueFramework_spec.ts verifying:
- Split button renders with correct options
- Primary action executes default navigation
- Dropdown options execute alternative navigation behaviors
- Behavior adapts correctly based on resource configuration

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:55 -07:00
Matt Blenkinsop 8a1dd87c70
Bug 40191: Update Cypress tests for split button navigation changes
Updates existing test selectors and assertions to work with the new split
button UI pattern:

- Admin/RecordSources_spec.ts: Update button selectors
- ERM tests (Agreements, Licenses, Packages, Titles, Dialog): Update form save expectations
- InfiniteScrollSelect_spec.ts: Update interaction patterns
- Preservation/Trains.ts: Update save button selectors

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:55 -07:00
Matt Blenkinsop 00f3c8dd99
Bug 40191: Extend split button navigation to sticky toolbar
Adds the same split button navigation options to the sticky toolbar that
appears when scrolling resource forms, ensuring consistent functionality
regardless of scroll position.

Components modified:
- Toolbar.vue: Support for dropdown button actions
- ResourceFormSave.vue: Emit navigation events for toolbar
- VendorResource.vue: Wire up toolbar navigation handlers

Maintains UX consistency between main form buttons and sticky toolbar.

Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:55 -07:00
Matt Blenkinsop 23afa83ecc
Bug 40191: Change button text from 'Submit' to 'Save' for clarity
Updates ResourceFormSave component button labels:
- "Submit" → "Save" (more intuitive for editing forms)
- "Submit and [action]" → "Save and [action]"

Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:54 -07:00
Matt Blenkinsop a61e410e11
Bug 40191: Add split button UI for alternative post-save navigation
Introduces a split button pattern allowing users to override the default
post-save behavior:
- Primary button executes default action
- Dropdown menu provides alternative navigation options
- Adapts menu options based on resource configuration

Components modified:
- New DropdownButtons component for split button UI
- Updated ButtonSubmit to support dropdown actions
- Extended ResourceFormSave with action handlers
- Applied to VendorResource and ToolbarButton

The UI adapts based on resource configuration:
- "show" default: offers "continue editing" and "return to list"
- "edit" default: offers "show" and "return to list"
- "list" default: offers "show" and "continue editing"

Test plan:
1) Enable ERM and navigate to the agreements form
2) At the bottom, there should now be a button for "Submit" and a dropdown with two options
 - Submit and continue editing
 - Submit and return to list
3) Fill out the form three times, testing a different button each time
4) Click to edit one of your agreements
5) The buttons should operate the same but now they will start with 'Submit' instead of 'Save'
6) Navigate to licenses - for the purposes of testing the DO NOT PUSH commit has set this to have the behaviour where it returns to the list on save
7) On the form, the list option should be gone from the dropdowns and replaced by "Submit and show"
8) The buttons should all operate as described
9) Navigate to packages - for the purposes of testing they have been set to default to staying on the form when saving
10) The option to continue editing should be gone, replaced with the option to return to the list
11) The buttons should work as described

Sponsored-by: Karlsruhe Institute of Technology (KIT)
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:54 -07:00
Matt Blenkinsop 317c548a07
Bug 40191: Apply configurable post-save navigation to all resource forms
Extends the navigation behavior system to all Vue resource components:
- EHoldings packages and titles (ERM)
- Licenses (ERM)
- Record sources (Admin)
- Preservation trains
- Vendors
- SkeletonResource template

Each resource can now define its preferred post-save behavior via the
on_save_navigation prop.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:53 -07:00
Matt Blenkinsop 1fc9fd20a6
Bug 40191: Add configurable post-save navigation for Vue resource forms
This patch introduces a new navigation behavior system for resource forms,
allowing each resource type to define its default post-save action (stay on
form, show detail, or return to list).

Changes:
- Add `on_save_navigation` prop to ResourceFormSave component
- Extend base-resource composable with navigation configuration
- Implement navigation logic in ButtonSubmit component
- Apply to AgreementResource as initial implementation

This provides a more flexible UX by letting different resource types have
appropriate default behaviors while maintaining consistent patterns.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 09:25:46 -07:00
Matt Blenkinsop e0a818979a
Bug 38262: (follow-up) Fix cypress tests
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-22 07:30:09 -07:00
Martin Renvoize a71c3a5173
Bug 33308: (QA follow-up) Use simplified File::Transport API from bug 40811
This follow-up updates runreport.pl to use the newer simplified
File::Transport API introduced by bug 40811, which provides:

- Automatic connection management (no explicit connect/disconnect)
- Simplified directory handling via options hash
- Cleaner, more maintainable code

The upload functionality now uses a single upload_file() call with
a path option instead of requiring separate connect() and
change_directory() calls.

Also fixes minor inconsistencies in error messages (missing colons
and escaped quotes).

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 15:28:18 -07:00
Jake Deery 875b35145f
Bug 33308: Add S/FTP upload support to runreport.pl
This patch adds S/FTP upload support to runreport.pl by adding additional command-line flags that will, when utilised, deposit a report onto the configured S/FTP Server.

== TO TEST ==
a)  Goto /cgi-bin/koha/admin/file_transports.pl?op=add_form
b)  Configure an S/FTP Server
    *)  If one needs an S/FTP Server to utilise, contact me directly
c)  Configure an SQL report under Reports > Create from SQL
    *)  If you're stuck, you can use `SELECT * FROM items` to select every item in k.t.d to report on
== APPLY PATCH ==
d)  Run `./misc/cronjobs/runreport.pl` without arguments
    *)  Notice `--sftp-dest-id` and `--filename`
    *)  `--sftp-dest-id` specifies the ID of the configured server to use, which can be garnered from the URL when editing the server (look for the file_transport_id parameter)
    *)  `--filename` optionally specifies what the file should be called when uploaded, and can be customised with bash variables if necessary
e)  Run `./misc/cronjons/runreport.pl --sftp-dest-id 1 1`
    *)  Notice the script outputs nothing, indicating success
    *)  On your S/FTP server, notice a report now named report(id)-(date).csv
    *)  Notice that the outputted file contains a CSV-formatted report
f)  Run `./misc/cronjobs/runreport.pl --format html --sftp-dest-id 1 1`
    *)  Notice the script outputs nothing, indicating success
    *)  On your S/FTP server, notice a report now named report(id)-(date).html
    *)  Notice that the outputted file contains an HTML-formatted report
g)  Run `./misc/cronjons/runreport.pl --filename custom.csv --sftp-dest-id 1 1`
    *)  Notice the script outputs nothing, indicating success
    *)  On your S/FTP server, notice a report now named custom.csv
    *)  Notice that the outputted file contains a CSV-formatted report
h)  Run `./misc/cronjons/runreport.pl --format html --filename custom.somehtml --sftp-dest-id 1 1`
    *)  Notice the script outputs nothing, indicating success
    *)  On your S/FTP server, notice a report now named custom.somehtml
    *)  Notice that the outputted file contains an HTML-formatted report
== SIGN OFF ==

Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 15:28:18 -07:00
Martin Renvoize ac64e18ce6
Bug 40811: (follow-up) Fix list context bug and add comprehensive tests
This patch fixes a critical bug where plain_text_password() was called
in list context during hash construction. When a transport has no
password, the method's bare 'return' statement returns an empty list ()
in list context rather than undef, causing all subsequent key-value
pairs to shift positions. This resulted in malformed connection
parameters being passed to Net::SFTP::Foreign and Net::FTP.

Example of the bug:
  my %hash = (
    user => 'john',
    password => $self->plain_text_password,  # Returns () when no password
    timeout => 10
  );
  # Results in: (user => 'john', timeout => 10) instead of
  # (user => 'john', password => undef, timeout => 10)

Changes to code:
- Wrap plain_text_password calls with scalar() to force scalar context
  in both FTP.pm and SFTP.pm
- Make SFTP key and password authentication mutually exclusive using
  ternary operator
- Prefer key-based auth over password when both are available (SFTP)

Test plan:
1. Run t/db_dependent/Koha/File/Transport/FTP.t - should pass
2. Run t/db_dependent/Koha/File/Transport/SFTP.t - should pass
3. Verify tests cover:
   - Scalar context behavior with no password
   - FTP authentication with proper parameter passing
   - SFTP password-only authentication
   - SFTP key-only authentication
   - SFTP key preference when both key and password exist

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 15:28:17 -07:00
Martin Renvoize b75d07acd5
Bug 40811: Implement dual API for file transports with simplified auto-management
This patch implements a dual API design for Koha::File::Transport, providing
both a simplified auto-managing API and a traditional explicit API for maximum
flexibility and ease of use.

DUAL API DESIGN
===============

Simplified API (Recommended):
  - Automatic connection management (no connect/disconnect needed)
  - Flexible per-operation directory control via options hashref
  - Stateless operations safe for concurrent usage

  Example:
    $transport->upload_file($local, $remote, { path => '/custom/' });
    $transport->download_file($remote, $local);  # Uses download_directory
    $transport->list_files({ path => '/incoming/' });

Traditional API (Explicit Control):
  - Manual connection/directory management when needed
  - Stateful directory operations via change_directory()
  - Ideal for multiple operations in the same directory

  Example:
    $transport->change_directory('/work/');
    $transport->upload_file($local, 'file1.txt');
    $transport->upload_file($local, 'file2.txt');
    my $files = $transport->list_files();

The APIs can be mixed - calling change_directory() explicitly switches to
manual mode and disables auto-management for subsequent operations.

STANDARDIZED TEMPLATE METHOD PATTERN
=====================================

All transport methods now follow a consistent pattern with clear separation
between public API (parent class) and protocol implementation (subclasses).

Public methods in Koha::File::Transport:
  - connect()          → Resets directory state, calls _connect()
  - disconnect()       → Resets directory state, calls _disconnect()
  - change_directory() → Sets manual mode flag, calls _change_directory()
  - upload_file()      → Manages connection/directory, calls _upload_file()
  - download_file()    → Manages connection/directory, calls _download_file()
  - list_files()       → Manages connection/directory, calls _list_files()
  - rename_file()      → Ensures connection, calls _rename_file()

Subclass responsibilities (SFTP/FTP/Local):
  - Implement _connect() - Protocol-specific connection logic
  - Implement _disconnect() - Protocol-specific disconnection logic
  - Implement _change_directory() - Protocol-specific directory change
  - Implement _upload_file() - Protocol-specific upload logic
  - Implement _download_file() - Protocol-specific download logic
  - Implement _list_files() - Protocol-specific listing logic
  - Implement _rename_file() - Protocol-specific rename logic
  - Implement _is_connected() - Protocol-specific connection check

Test plan:
  prove t/db_dependent/Koha/File/Transports.t

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 15:28:17 -07:00
Tomás Cohen Arazi ac9944f7b0
Bug 29668: Adapt to current guidelines
The added patches make some changes:

* $c->req->json is used to access the request body, as explained by the
  Mojolicious OpenAPI plugin author it should be done.
* Instead of calling `$basket->to_api` we use the `$c->objects->to_api`
  helper which deals with calling context (embeds, etc). Otherwise it
  would require doing it manually if embeds are added.
* Tests are grouped together the same way the rest of them are.

To test:
1. Apply this patches
2. Run:
   $ ktd --shell
  k$ prove t/db_dependent/api/v1/acquisitions_baskets.t
=> SUCCESS: Tests pass!

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 15:28:16 -07:00
Tomás Cohen Arazi ca8134f85d
Bug 29668: Move tests
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 15:28:16 -07:00
Julian Maurice 5e0a72f8a8
Bug 29668: Add API route to add a basket
Example usage:

POST /api/v1/acquisitions/baskets

{
    "vendor_id": 1,
    "name": "Basket #1",
}

Test plan:
1. Try requesting this endpoint with your favorite API tool
   (I recommend https://github.com/frigus02/RESTer)
2. Run `prove t/db_dependent/api/v1/acquisitions_baskets/post.t`

Edit (tcohen): Re-did the commit with some fixes in the spec

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 15:28:09 -07:00
Lucas Gass 614020effa
Bug 40777: (follow-up) Use var instead of let when declaring specific_dt_errors
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 13:32:02 -07:00
Pedro Amorim 63d1023317
Bug 41237: (QA follow-up): Utilize available logged in user object
Instead of re-fetching the patron object.

This also adjusts tests:
prove t/db_dependent/Koha/ILL/Requests.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:21 -07:00
Pedro Amorim de5dfef766
Bug 41237: Add tests
Test plan, k-t-d:
1) Apply tests patch, run:
prove t/db_dependent/Koha/ILL/Requests.t
2) Notice it fails. Apply the second patch. Repeat. Notice it passes.

Test failure explanation (before fix patch):
    #   Failed test 'Cardnumber not supplied and couldnt find additional_contents for all libraries. Dont show copyrightclearance'
    #   at t/db_dependent/Koha/ILL/Requests.t line 1067.
    #          got: 'copyrightclearance'
    #     expected: 'commit'

Its searching the copyrightclearance additional_contents for the provided branchcode param (illrq->patron->branchcode) (and finding one) when it shouldn't. That parameter is only to be utilized for the newly created ILL request's library. The branchcode filter of the additional_contents must the be logged in user's cardnumber, not params->{branchcode}. If no logged in user exists and this stage is reached (ILLOpacUnauthenticatedRequest), then only 'All libraries' ILLModuleCopyrightClearance are to be fetched, as expected.

Human test plan, before applying patches:
1) Enable ILLModule
2) Logged in as koha:koha, create an OPAC request, visit:
<opac_url>/cgi-bin/koha/opac-illrequests.pl?op=add_form&backend=Standard
3) Pick any library not Centerville (the default library of the 'koha' user)
4) Notice the created request's library is CPL (Centerville). It ignored the library selection from the form.
5) Apply patches. Repeat. Notice the request's library is now the one that was selected on the form.

Signed-off-by: Stephen Graham <s.graham4@herts.ac.uk>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:21 -07:00
Pedro Amorim ee348649a2
Bug 41237: Update fetch of patron's branchcode to search additional_contents by
Signed-off-by: Stephen Graham <s.graham4@herts.ac.uk>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:20 -07:00
Nick Clemens aef17e613d
Bug 40846: Improve handling of ignored records
Currently, a job is marked failed if there are no records added or updated.
We should take ignored records into account.

Additionally, I mark the job failed when the number of records processed
doesn't equal the number of records in the batch.

To test:
1) Export some records from your Koha, include some with items
2) Stage and import those records into Koha, matching on biblionumber,
   ignoring matcheds, updating items
3) View the import job, it is marked failed
4) View the batch, all records imported
5) Apply patch, restart all
6) Stage/import as before
7) Job is now marked finished
8) Records imported as before

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-01-21 07:44:20 -07:00
Victor Grousset/tuxayo f39a2118cd
Bug 40777: (QA follow-up): remove trailing space from translatable string
Otherwise it ends up as "Audit " in the .po

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:19 -07:00
Jonathan Druart e549d21441
Bug 40777: Add POD
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:19 -07:00
Jonathan Druart 3957a43e71
Bug 40777: Add tests for for_biblio
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:18 -07:00
Jonathan Druart a49af4a528
Bug 40777: Flatten arrays
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:18 -07:00
Jonathan Druart bc4db881f4
Bug 40777: Add tests for empty_title
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:17 -07:00
Jonathan Druart 5da10c0ecf
Bug 40777: Add tests for nonexistent_AV
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:17 -07:00
Jonathan Druart 85b8096284
Bug 40777: Add tests for errors_in_marc
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:16 -07:00
Jonathan Druart 4551a2d1ba
Bug 40777: Add tests for invalid_item_type
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:16 -07:00
Jonathan Druart 368e8124c5
Bug 40777: Add tests for invalid_item_library and no_item_type
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:15 -07:00
Jonathan Druart a4c0746370
Bug 40777: Move all biblio tests to the new package
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:15 -07:00
Jonathan Druart fba31a1035
Bug 40777: Add info in the alert
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:14 -07:00
Jonathan Druart 305da27e65
Bug 40777: Add 'Audit' button in the toolbar
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:14 -07:00
Jonathan Druart b88dd24139
Bug 40777: Introduce Koha::Database::DataInconsistency
test plan:
1. Export a record with an item from the staff interface as MARCXML (Save > MARCXML).
2. Edit the file and remove the 952$a (home library) and 952$b (current library).
3. Delete the item and record in the staff interface.
4. Import the exported record and item from the MARCXML file:
   - Cataloging > Import > Stage for import
     . Settings > Format: MARCXML
   - Stage for import
   - View batch link on the job details page (assuming successfully staged)
     . New record framework: change to Books, Booklets, Workbooks
5. Turn on the browser developer tools and view the console.
6. Access the details page for the new record.
7. Note that you get:
   - A "Processing" dialog box on screen
   - A browser pop-up window/dialog box with:
       Something went wrong when loading the table.
       500: Internal server error
   - Click OK
   - The console shows:
      . Failed to load resource: the server responded with a status of 500 (Internal Server Error)
      . Got 500 (Internal Server Error)
      . DataTables warning: table id=holdings_table - Ajax error. For more information about this error, please see https://datatables.net/tn/7
8. Run misc/maintenance/search_for_data_inconsistencies.pl, you get these result (the itemnumber may be different):
    == Not defined items.homebranch and/or items.holdingbranch ==
    	* Item with itemnumber=973 does not have home and holding library defined
    => Edit these items and set valid homebranch and/or holdingbranch
    (↑↑ don't do it, it's part of the message content)
9. Apply the patch.
10. Restart everything.
11. Refresh the details screen for the imported record.
12. You now get the same as step 7, but with added "Have a look at the "Audit" button in the toolbar" text in the browser pop-up dialog box:
    - A "Processing" dialog box on screen
    - A browser pop-up window/dialog box with:
        Something went wrong when loading the table.
        500: Internal server error
        Have a look at the "Audit" button in the toolbar
    - Click OK
    - The console shows:
      . GET http[URL for the record....] 500 (Internal Server Error)
      . Got 500 (Internal Server Error)
      . DataTables warning: table id=holdings_table - Ajax error. For more information about this error, please see https://datatables.net/tn/7
13. Click on the Audit button.
14. You get a yellow box at the top of the record with:
      Errors found
      Item with itemnumber=973 does not have home and holding library defined
    (Note that there is still the "Processing" dialog box, you can edit the item by select the "Items" option in the sidebar menu and then edit the item details - the home and current library files will be auto-populated with the current library, Centerville with KTD)
15. Repeat step 8, you get the same results.
16. The tests pass: prove t/db_dependent/Koha/Database/DataInconsistency.t
17. Check the details page for another record that doesn't have any data issues - note the 'Audit' toolbar item, and if clicked should show a blue box at the top of the record with "No data inconsistency found."

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:44:13 -07:00
Owen Leonard 48960e09b0
Bug 41327: Silence Sass deprecation warnings
Some warning are about issue we can address (legacy-js-api for
instance), and others we have to wait to be fixed in Bootstrap
(color-functions, global-builtin).

For now I think we should silence these warnings.

To test, apply the patch and run 'yarn build.' There should be no
warnings.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-21 07:35:36 -07:00
Nick Clemens f21255ca54
Bug 41416: Add skip_fix_priority option to ModReserve
This patch adds a new parameter to ModReserve: skip_fix_priority

The page for altering priority ensures each hold has a unique rank, so doesn't allow for setting two
holds to the same rank, so we don't need to reorder everything until the end (in case of some cancellations etc)

This patch also makes FixPriority exported and updates the name as it is already called in a few places outside of
C4:Reserve

To test:
 1 - Place many holds on a record, 50 - 150
     https://github.com/kidclamp/handy-koha-script/blob/main/randhold.pl
 2 - View the record in the staff interface:
     http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=312
 3 - Click on the 'Holds' tab
 4 - Adjust the priority of several holds using the dropdowns
 5 - Open the network tab in the browser console (F12) to view the timing
 6 - Click 'Update hold(s)' and note the time
 7 - Apply patch, restart all
 8 - Repeat
 9 - Confirm timing is better and numbering updates correctly
10 - Sign off!

Note: The script adds holds that may be marked as 'lowestPriority' these will always all group at the bottom of the record
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:18 -07:00
Andreas Roussos 881d0c143c
Bug 41481: Change Koha Community website URL scheme to https
Since version 25.11.00 (also valid in current main), while
adding or editing a record, launching the plug-in to help
complete the MARC21 006/008 fields results in a browser
pop-up with the message "Can't validate the xml data from
(...)/marc21_field_00{6,8}.xml".

The actual problem lies with how the value builder plugins
(marc21_field_00{6,8}.pl) try to validate the relevant XML
files against the Schema Definition (marc21_field_CF.xsd),
the latter containing two references to the K-C.org website
that still use the 'http' URL scheme.

This patch fixes that.

Test plan:
1) In a MARC21 instance, create a new record by going to:
   Cataloging > New record > Books, Booklets, Workbooks,
   then click inside the 008 field so it becomes populated.
2) Click the Tag editor icon next to field 008. Notice how
   you get a pop-up window in your browser with the error:
   "Can't validate the xml data from (...)/marc21_field_008.xml".
3) Apply this patch.
4) Repeat step 2) -- this time the tag editor should launch
   correctly.
5) For extra credit, repeat the Test plan this time testing
   field 006 -- its tag editor should work without problems
   after applying the patch.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:18 -07:00
Emmi Takkinen 05204294bb
Bug 41334: Move modified_holds tables column settings under Tools section
Bug 36135 added new tool to batch modify holds and two new datatables.
Table holds_to_modify was added to "Table settings" page under section
Tools>Page: batch_hold_modification but table modified_holds is under
Circulation>Page: holds section. It should be also moved to Tools section.

To test:
1. Navigate to page "Table settings" and open section Circulation.
2. Jump to Page: holds.
=> Note that modified_holds tables settings are displayed under this page.
3. Apply this patch.
4. Open Tools section and jump to page batch_hold_modification.
=> Note that modified_holds tables settings are now displayed under this
page and they are no longer present under Circulation>Page: holds.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:17 -07:00
Martin Renvoize 3853b679c3
Bug 41438: (follow-up) Fix JavaScript validation issues
This follow-up patch corrects two JavaScript issues in the batch
modify holds form:

1. Fixes typo: 'suspened' -> 'suspended' on line 637
   This typo prevented the suspended status check from working
   correctly when determining if any selected holds are suspended.

2. Fixes ineffective validation logic on line 643
   When a suspend date is provided without explicitly setting the
   suspend status, the code now properly updates the form field
   value instead of just setting a local variable. Changes:
   - Old: new_suspend_status = 1;
   - New: $("#new_suspend_status").val("1");

   This ensures that setting a suspend date automatically sets
   the suspend status to "Suspend" as intended, allowing the
   behavior to match the server-side logic.

Test plan:
1. Navigate to Tools->Batch modify holds
2. Search for and select some holds
3. Set only a "Suspend until" date without changing "Suspend holds"
4. Click "Modify holds"
5. Verify the holds are suspended with the specified date
6. Confirm the form properly auto-selects "Suspend" when a date
   is provided

Signed-off-by: Emmi Takkinen <emmi.takkinen@koha-suomi.fi>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:17 -07:00
Emmi Takkinen 8798d60bec
Bug 41438: Do not always unsuspend holds when using batch modify holds tool
If one modifies suspended holds e.g. changes their pickup library, holds
are unsuspended. This happens due error in logic of checking if "Suspend
holds" selection is empty. This patch fixes this by moving the if statement
for checking new suspended status before doing anything else for the value.

To reproduce:
1. Navigate to Tools->Batch modify holds.
2. Search suspended holds.
3. Select a hold to modify and change it's pickup library, but leave
"Suspend holds" selection empty.
4. Press "Modify holds".
=> Note that hold is now unsuspended.
5. Apply this patch, start services again if needed.
6. Repeat steps from 2. to 4.
=> Note that hold is not suspended.
=> Confirm that suspending and unsuspending with and without suspend date
still works as expected.

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:16 -07:00
Jonathan Druart 9e7401ea1b
Bug 41545: Remove JS warning "redeclaration of let filters_options"
On the suggestions page more than one patron search is injected into the
page that leads to JS warnings when the JS variable is defined with
"let". We should use "var" in this case.

Test plan:
/cgi-bin/koha/suggestion/suggestion.pl?op=add_form&branchcode=CPL
And notice that the warning does no longer appear when this patch is
applied.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Brendan Lawlor <blawlor@clamsnet.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:16 -07:00
Lari Taskula 8673c798d5
Bug 27115: Use start-stop-daemon to stop in koha-sip
There has been an issue where a restart of koha-sip would sometimes not bring
the SIP server back alive. This is because we have not been waiting for
koha-sip --stop to actually stop the SIP server(s). It is then possible for a
restart to attempt the --start command while a previous instance of SIP server
is still running. As the --start command in this case is not allowed, we would
then end up not starting the SIP server(s) at all.

This patch replaces daemon --stop with start-stop-daemon --stop to ensure
koha-sip daemon actually stops.

start-stop-daemon provides us the --retry option, which we are able to utilize
in waiting for the stop process to finish.

To test:
1. Before applying this patch
2. Run this command

export SIPINSTANCE="your_koha_instance_name"

You can your_koha_instance_name using the koha-list command

3. Configure a SIP server (add some min_servers, e.g. 6, to SIPConfig.xml)
4. koha-sip --enable $SIPINSTANCE
5. koha-sip --verbose --start $SIPINSTANCE
6. Observe "Starting SIP server for ..."
7. koha-sip --verbose --stop $SIPINSTANCE; ps aux | grep $SIPINSTANCE-koha-sip
8. Observe "Stopping SIP server for instancename"
9. In the output, observe the number of SIP servers you defined in step 3, plus
   one.
10. koha-sip --verbose --start $SIPINSTANCE
11. Apply this patch, or edit the koha-sip directly to get changes in place
in your environment, e.g. using command

nano $(which koha-sip)

12. koha-sip --verbose --stop $SIPINSTANCE; ps aux | grep $SIPINSTANCE-koha-sip
13. Observe "Stopping SIP server for instancename"
14. In the output, observe no more SIP servers alive

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:15 -07:00
Laura_Escamilla 56d69d9f4d
Bug 40726: Prevent closing the user menu when clicking off of the dropdown
To test:

1. In the staff interface click on the user menu (top right corner where it has the user and library branch).
2. Click the gear icon.
3. Try to click a dropdown but miss it and click outside of it (still in the grey box area).
4. View the menu collapsing.
5. Apply the patch & restart all. Refresh your page.
6. Retry steps 1-3. Notice now that when you click within the grey box area around the drop down the user menu no longer closes. However, if you click outside of the dropdown menu (anywhere else on the interface screen) the user menu closes.
7. Sign off — have an amazing day! :D

Signed-off-by: noah <noah@inlibro.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:15 -07:00
Owen Leonard 7f2aedfd3e
Bug 41623: (follow-up) Correct JS variable name
This patch fixes the variable name meant to display the circulation desk
name when the waiting hold was checked in at a specific circ desk.

A revised test plan to test this specific case:

- Install a new langage
  gulp po:update --lang fr-CA
  ./misc/translator/translate install fr-CA
- Log in to the staff client and set your circulation desk
  - Requires that the UseCirculationDesks preference is enabled and at
    least one desk is defined in Administration -> Desks.
- Place a hold to be picked up at your logged-in library
- Check in the item on hold and confirm the hold
- Go to the record details page. In the holdings table you should see
  this under "Status" for that item:
    --> "Waiting at [library], undefined since [date]."
- Check the translation files
  cd misc/translator/po/
  git grep "Waiting at" fr-CA*
    --> There is no "Waiting at %s, %s since %s."

- Apply patch
- Update strings again
- Check translation files again
    --> The string should be there
- Translate the string (making sure to remove the fuzzy line if any)
- Install the translation again (only the translate install part)
- Check the record details page again. You should now see:
    --> "Waiting at [library], [desk] since [date]."

Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:14 -07:00
Caroline Cyr La Rose 204b278caa
Bug 41623: Fix translation pickup in catalogue_details.inc
This patch redoes the fix that was made in bug 38450 to make sure
the "Waiting at ... since ..." string is picked up by the translation
tool. It's a parenthesis move.

See bug 38450 for explanation.

_("This %s is picked").format(foo)
_("This %s is NOT picked".format(bar))

To test:
1. Install a new langage
gulp po:update --lang fr-CA
./misc/translator/translate install fr-CA

2. Place a hold

3. Check in the item on hold and confirm the hold

4. Go to the record details page
   --> Waiting at ... since ... is in English

5. Check the translation files
cd misc/translator/po/
git grep "Waiting at" fr-CA*

--> There is no "Waiting at %s, %s since %s."

6. Apply patch

7. Update strings again

8. Check translation files again
--> The string should be there

9. Translate the string (making sure to remove the fuzzy line if any)

10. Install the translation again (only the translate install part)

11. Check the record details page
    --> It should be translated!

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

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:14 -07:00
Pedro Amorim c1cd1b0daf
Bug 41615: Fix Licenses_spec.ts
Before applying patch, run:
cypress run --spec t/cypress/integration/ERM/Licenses_spec.ts
Notice it fails.
Apply patch and run it again. Notice it passes.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-16 14:07:13 -07:00
Lucas Gass c1efa1f810
Bug 41456: (follow-up) Change item_type to item_type_id and add _strings in test data
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-14 08:50:30 -07:00
Lucas Gass 59382086ef
Bug 41409: DBIC
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-14 08:50:17 -07:00
Wainui Witika-Park 277bc4e294
Bug 36849: Add more tags to Elasticsearch mapping for title, title as phrase
Add additional mappings to the title index for MARC21: 440a, 600t, 800t, 810t, 830np

Sponsored-by: Educational Services Australia SCIS
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:20:08 -07:00
Lucas Gass 71f1fb48d1
Bug 28701: Add mexlength to the API spec
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:20:08 -07:00
Marcel de Rooy 2c5efb40b1
Bug 28701: (QA follow-up) Make primary_contact_method visible
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:20:07 -07:00
Alexis Ripetti 4b34532dec
Bug 28701: Adding primary_contact_method to the REST API
This patch adds the primary_contact_method field to the api response for patrons

To test:
1 - GET http://localhost:8081/api/v1/patrons/51
2 - Look for primary_contact_method, it's not there
3 - Apply patch
4 - GET http://localhost:8081/api/v1/patrons/51
5 - Look for primary_contact_method, it's not there

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:20:07 -07:00
Nick Clemens 7837bfa00e
Bug 28495: Add hint to branchcode input
This patch adds a hint to the library creation form warning the user
about what characters are valid in the "Library code" field.

To test, apply the patch and go to Administration -> Libraries -> New
library. Under the "Library code" input you should see a hint, "Note:
Whitespace is not permitted. The use of hyphens, dashes, or other
punctuation may cause searching issues."

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:20:06 -07:00
Owen Leonard 094a2cbeaf
Bug 39927: (follow-up) Add permission check to display of page section
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:20:06 -07:00
Owen Leonard 3f4d5ca3d9
Bug 39927: Add permissions check to PatronSelfRegistrationAlert on home page
This patch adds a permissions check around the display of
PatronSelfRegistrationAlert information on the staff interface home
page.

To test, apply the patch and follow the test plan on Bug 35028:

1. Create patron category SELFREG
2. Enable PatronSelfRegistration and set
   PatronSelfRegistrationDefaultCategory to SELFREG and
   PatronSelfRegistrationAlert to "Show"
3. Go to OPAC, logout if needed, and create a new account. Remember the
   value you set for home library.
4. Go to staff interface, set the current library to the one you set in
   step 3.

Now view the staff interface home page as two different users:
- A user with either CAN_user_borrowers_edit_borrowers or
  CAN_user_borrowers_list_borrowers permission.
- A user with neither.
- The self-registration information should display or not depending on
  the user's permission.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:20:05 -07:00
Petro Vashchuk b018e39c3a
Bug 40584: show 001 in Advanced Editor and preserve its existing value instead of overwriting it
This patch makes the 001 field visible when editing a record in the advanced
editor and fixes a bug where, if the syspref autoControlNumber was set to
"generated as biblionumber," editing a record through the advanced editor
would overwrite the existing 001 value.

1. Create a new record with a custom "001 Control Number" MARC field, don't use biblionumber as control number.
2. In the system preferences set EnableAdvancedCatalogingEditor to "enable" and autoControlNumber to "generated as biblionumber"
3. Edit record, switch to advanced editor either through option in the "Settings" dropdown menu or switch on top right of the page.
4. In the advanced editor see that it doesn't show 001 field, no need to change anything just use the save button to save the record.
5. On the record page see MARC preview, it's gonna be overwritten by the value of biblionumber.
6. With basic editor change the 001 field to your old custom value.
7. Apply the patch.
8. Use the advanced editor again, 001 field should be visible now.
9. Use the save button to save the record. Make sure that your custom value is still there by using MARC preview on records page.

Signed-off-by: Anneli Österman <anneli.osterman@koha-suomi.fi>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:20:05 -07:00
Owen Leonard 48f0c7a8dc
Bug 39043: (follow-up) Re-number the steps, add back button to more steps
Signed-off-by: Brendan Gallagher <brendan@bywatersolutions.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:20:04 -07:00
Owen Leonard b6062798e9
Bug 39043: Style improvement to guided reports controls
This patch makes some markup and CSS changes to improve the style of
controls in the build guided report process.

To test, apply the patch and rebuild the staff interface CSS.

- Go to Reports -> Create guided report
- In each step of the process, confirm that navigation buttons are
  styled with Font Awesome arrow icons.
  - In step 3, confirm that the "Add" and "Delete" buttons are
    well-styled and aligned well between the two selection boxes.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Brendan Gallagher <brendan@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:19:57 -07:00
Lucas Gass 557ac9900e
Bug 39320: Update yarn.lock
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:38 -07:00
Tomas Cohen Arazi bcb413abfc
Bug 39320: (QA follow-up): Fix tidy of ERM Home.vue
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:38 -07:00
Pedro Amorim 79dfd2b9a1
Bug 39320: (QA follow-up): Simplify widget tests
This simplifies the tests, only visiting the homepage once and not navigating outside of it.

This must pass 10/10:
for i in {1..10}; do   cypress run --spec t/cypress/integration/ERM/ModuleDashboard_spec.ts; done

Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:37 -07:00
Jonathan Druart ae5dc61be3
Bug 39320: Simplify ERMCounts
* Move count endpoint to the definition list
* Remove useless try-catch - we need to retrieve the response from
  fetch, not what we do by default. We could add them back later but not
  feeling important for now
* Remove not needed function createCountText

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:36 -07:00
Jonathan Druart 42cdab5fda
Bug 39320: (follow-up) Use __nx for languages with 3 plural forms (or more)
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:36 -07:00
Pedro Amorim 538688cccf
Bug 39320: (ERMCounts QA follow-up): Clean-up previous ERMCounts implementation
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:35 -07:00
Pedro Amorim cb4d9e27f9
Bug 39320: (ERMCounts QA follow-up): Update Cypress
cypress run --spec t/cypress/integration/ERM/ModuleDashboard_spec.ts

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:35 -07:00
Pedro Amorim 0a6bebc1d8
Bug 39320: (ERMCounts QA follow-up): Reimplement loading
Now present 'Loading' for each resource count that's waiting, rather than hang the whole widget waiting for all responses

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:34 -07:00
Pedro Amorim b910388915
Bug 39320: (ERMCounts QA follow-up): Remove documents as an endpoint for listing that does not exist
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:34 -07:00
Pedro Amorim c3179a8145
Bug 39320: (ERMCounts QA follow-up): Use existing count method from the API client
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:33 -07:00
Pedro Amorim b621632667
Bug 39320: (QA follow-up): Fix widget with settings
This was broken from the rewrite into bug 38201. Fixed here.
Without this patch, if you click 'settings' on the licenses widgets and pick different values, you get errors on the console and everything breaks.

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:33 -07:00
Pedro Amorim 362c6b47bd
Bug 39320: (QA follow-up): Remove big 'Processing...' when loading table widgets
This prevents the big centered 'Processing...' from showing when moving widgets around the dashboard.
Keeps the 'Loading...' at the widget level for table widgets, same as any other widget.

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:32 -07:00
Pedro Amorim 24b0abb34b
Bug 39320: (QA follow-up): Only show 'drop widgets here' area when actively dragging
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:32 -07:00
Jonathan Druart 4fb7cc3d98
Bug 39320: Use __n for languages with 3 plural forms (or more)
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:31 -07:00
Pedro Amorim d57bb45ce3
Bug 39320: (QA follow-up): Style new ERMCounts list
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:31 -07:00
Pedro Amorim 0e94b8457e
Bug 39320: (QA follow-up): Fix ERMCounts translatability
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:30 -07:00
Pedro Amorim 03f6673556
Bug 39320: (QA follow-up): Display 'Uknown status' if not one of the expected job statuses
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:30 -07:00
Pedro Amorim 5a133c8756
Bug 39320: (QA follow-up): Add an empty drop area placeholder
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:29 -07:00
Jonathan Druart 1a5de8d08f
Bug 39320: (follow-up) Add counts API tests
Prevent tests to fail if data in DB

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:29 -07:00
Pedro Amorim aab1c7e37e
Bug 39320: (QA follow-up): Use escape_str instead of .escapeHtml
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:28 -07:00
Pedro Amorim aefe6a8a51
Bug 39320: (QA follow-up): Add a handler for drag and drop
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:28 -07:00
Pedro Amorim 70b276c1d2
Bug 39320: (QA follow-up): Fix cypress
These tests have always passed for me running cypress on UI, before and after this patch.
CLI tests however, were flaky and often times failed.
It appears this is because each module refresh was flooding the server with HTTP requests causing the whole thing to bottleneck (?)
With this patch, all widget requests are stubbed for every widget test, and tests now pass on CLI consistently. Passed 10/10 for me, whereas before it failed more often than passed.

Run this:
for i in {1..10}; do   cypress run --spec t/cypress/integration/ERM/ModuleDashboard_spec.ts; done

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:27 -07:00
Pedro Amorim d782eebd91
Bug 39320: (QA follow-up): Removed unused show event
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:27 -07:00
Pedro Amorim d14eb00f05
Bug 39320: (QA follow-up) Prevent full page reload when clicking a license
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:26 -07:00
Pedro Amorim 44c65be394
Bug 39320: (QA follow-up): Fix counts links
Use router-link instead of href='#'

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:26 -07:00
Pedro Amorim e491363020
Bug 39320: (QA follow-up): Use localStorage instead of vue-cookies
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:25 -07:00
Pedro Amorim fc73aea182
Bug 39320: (QA follow-up): Add counts API tests
prove t/db_dependent/api/v1/erm_counts.t

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:25 -07:00
Pedro Amorim 6d839d199b
Bug 39320: (QA follow-up): Remove deep CSS rules
This CSS is in vue.css anyway

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:24 -07:00
Matt Blenkinsop 2fdce21dff
Bug 39320: Remove 'extends' from the Home component
Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:24 -07:00
Pedro Amorim b0c1a3d973
Bug 39320: (follow-up) Add 'move up' and 'move down' options
Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:23 -07:00
Pedro Amorim 188ab18ef0
Bug 39320: Preparation: Add base and wrapper components
As well as wrapper support components
base-widget.js composable

Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:23 -07:00
Pedro Amorim c42187c9c5
Bug 39320: Add Cypress tests
Test plan:
1) Enable ERMModule
2) Visit ERM:
<staff_url>/cgi-bin/koha/erm/erm.pl
3) Notice you now have a 'dashboard' homepage showing 4 different widgets
4) Click 'Open Widget Picker'. Remove and add different widgets. Close
5) Drag and drop the widgets around the dashboard, from left to right, top to bottom
6) Click the context menu (vertical dots) on each of the widgets. Click 'move to right', 'move to left', 'remove'.
7) On the 'Licenses needing action', notice there is a 'Settings' item option. Click that, test different settings values. Close settings.
8) Using the widget picker or context menu for the respective widgets, remove all widgets except one. Click away i.e. 'Agreements' on the left menu. Go back to home by clicking 'Home'. Notice your dashboard is as you left it.
9) Enable all widgets, rearrange them by dragging and dropping in different places. Repeat the previous exercise. Notice the dashboard keeps the widgets arranged as you left them.
10) Add some test data, on k-t-d run:
bash <(curl -s a0347d4199/bug_39320_test_data.sh)
11) Refresh the homepage dashboard, check the 'Counts' are correct and links work, check that the 'Latest SUSHI Counter jobs' table now populates and links to the data provider as well as respective job.
12) For 'Licenses needing action', test different settings values and confirm the table filters the results as expected.
13) As before, click away and comeback to ERM home. Notice your 'Licenses needing actions' settings are kept as you left them.
14) For the 'eUsage report' widget, notice it says 'No saved reports are availble to run'. Create a new usage report at:
<staff_url>/cgi-bin/koha/erm/eusage/reports
15) Click the 'Create report' tab, input the mandatory fields and enter a report name at the bottom of the page. Click 'Save report'.
16) Come back to ERM home. Notice the 'eUsage report' widget now shows a dropdown with a disabled 'Run' button.
Pick the report you just created. Notice the 'Run' button is now enabled. Click 'Run'. Notice you get the report results screen (will show nothing as there is no usage data in this test exercise).
17) k-t-d: Run cypress tests (reset_all beforehand to clear test data):
cypress run --spec t/cypress/integration/ERM/ModuleDashboard_spec.ts

Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:22 -07:00
Pedro Amorim 80d3b877d1
Bug 39320: Add 'home' left menu item to ERM
This is a bit hacky, but after hours of fighting with this I wasn't able to come up with a better soltuion.
I suspect stores/navigation.js is doing too much and changing too much of the default router-link behaviour but I may be wrong.

Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:22 -07:00
Pedro Amorim 28f75caa48
Bug 39320: Add FA icons to ERM
Would be great to improve the way we load icons to be available
everywhere in Vue instead of just the specific module but on a different
scope.

Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:21 -07:00
Pedro Amorim 4308261d62
Bug 39320: Add dashboard to ERM home
Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:21 -07:00
Pedro Amorim 6bbe1794f8
Bug 39320: Add ERM specific dashboard widgets
Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:20 -07:00
Pedro Amorim e7ad97908b
Bug 39320: Preparation: ERM counts API endpoint
Required for the follow-up ERMCounts widget

Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:20 -07:00
Pedro Amorim f417b4ca0c
Bug 39320: VueJS framework: Add option to have a component in dialog
Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:19 -07:00
Pedro Amorim 3e5ce26d5e
Bug 39320: Add new npm dependencies:
vue-draggable-next
vue-cookies

Sponsored-by: UKHSA (UK Health Security Agency)

Signed-off-by: Jeremy Evans <Jeremy.Evans@ukhsa.gov.uk>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:19 -07:00
Lari Taskula 61dc0bd36d
Bug 32483: Add OPAC notification on pending patron modification request
To test:
1. Apply patch
2. In OPAC, submit updates to your patron details under "Personal
   details"
3. Navigate back to "Personal details"
4. Observe text "Your have recently submitted updates to your personal
   details. A librarian will review your updates before applying them."
5. In staff client, approve modification changes
6. Navigate back to OPAC "Personal details"
7. Observe the notification has disappeared

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 15:03:12 -07:00
Lucas Gass b98f920541
Bug 38262: (follow-up) Add extended_attributes and _strings when inserting test data
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 13:52:06 -07:00
Matt Blenkinsop 070029e959
Bug 38262: (follow-up) Check for extended attributes before iterating
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 10:12:28 -07:00
Marcel de Rooy 5b5e402c20
Bug 40993: (follow-up) Add changes to etc/koha-httpd.conf
Good to have a test :)

Test plan:
Run xt/check_apache_conf.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-13 10:12:21 -07:00
David Cook 08bd622b25
Bug 40993: (QA follow-up) use L flag to stop the rewrite after PT action
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:37 -07:00
Marcel de Rooy 4209136411
Bug 40993: Add Apache RewriteRule for hitting value_builder pages
Test plan:
Copy this file to /etc/koha.
Restart Apache.
Login to staff and hit a value builder page directly, e.g.:
YOUR_SERVER/cgi-bin/koha/cataloguing/value_builder/marc21_field_005.pl
Verify that you got redirected to 403.
Verify that you can still edit a biblio record and open e.g. the leader
plugin form.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:36 -07:00
Jonathan Druart e8f4637f84
Bug 38262: (bug 38446 follow-up) Use a dedicated endpoint
Following what has been done on bug 38262 we need a separate endpoint to
retrieve additional fields for vendors.

We add another proxy for acquisition in acquisition-api-client: we do
not want to retrieve all the fields for acquisition as we have more
granular permissions than ERM.

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:36 -07:00
Matt Blenkinsop 52c3b54269
Bug 38262: Add additional fields UI support for acquisition vendors
Updates staff interface to expose additional fields functionality for
vendor records:
- Adds "Vendors" (aqbooksellers:vendor) to searchable tables list in
  additional fields administration page
- Configures VendorResource.vue to fetch extended_attributes with
  proper embeds (+strings for localized values)
- Sets extendedAttributesFieldGroup to display fields in "Details" tab
- Cleans up _strings computed field during vendor save operations

This completes the frontend implementation allowing staff to create,
manage, and view custom additional fields on vendor records.

Test plan:
1) Go to Administration > Additional fields and add an additional field for Vendors
2) Navigate to 'Acquisitions' and click 'New vendor'
3) Fill out the form, making sure to add something into your new additional field
4) Hit save
5) View and Edit your new Vendor and confirm that the additional field
   values appear and update as expected

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:35 -07:00
Matt Blenkinsop 0c4ccf6d6f
Bug 38262: Enable additional fields support for acquisition vendors
Implements backend support for additional fields on vendor records by:
- Adding AdditionalFields mixin to Bookseller/Booksellers classes
- Defining additional_field_values and extended_attributes relationships
  in Aqbookseller schema with proper table name filtering
- Updating REST API controller to handle extended_attributes during
  vendor creation and updates
- Adding vendor to AdditionalField resource type mapping

This provides the ORM-level support needed for vendors to have custom
additional fields, matching the existing pattern used for other resource
types like agreements, packages, and orders.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:35 -07:00
Matt Blenkinsop ee6f5a3ee9
Bug 38262: Add extended attributes support to vendors API specification
Updates the OpenAPI definitions to support additional fields on vendor
records.

This adds:
- extended_attributes array field to vendor schema definition
- _strings field for localized/computed string representations
- extended_attributes and +strings to available embed options

These changes allow the API to expose and accept additional field data
when retrieving or modifying vendor records via the REST API.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:34 -07:00
Jacob O'Mara 05423a313b
Bug 39522: (follow up) Add defensive checking before instantiating plugins object
Previously this could have caused issues if enable_plugins was disabled
in koha-conf as we were not checking enabled status before instantiating
an instance of the plugins object.

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:34 -07:00
Jonathan Druart 508a8de6f0
Bug 39522: Ignore the whole file for tidy
The prettier plugin for TT has been adjusted to correctly parse our TT
files: most of them does not have an opening head but have a closing
head, then have an opening body but no closing body.

To avoid the parser to fail we are commenting those lines in a
pre-process step in the plugin, then uncomment them in the tidy script.

Which means we actually cannot parse correctly formatted HTML files.

https://gitlab.com/koha-community/prettier-plugin-template-toolkit/-/blob/main/src/parser.ts?ref_type=heads#L242
export const preprocess: Parser<Node>["preprocess"] = (text) => {
    return text
        .replace("</head>", "<!--</head>-->")
        .replace(/<body(.*)/, "<!--<body$1-->")
        .replace("</body>", "<!--</body>-->");
};

https://git.koha-community.org/Koha-community/Koha/src/branch/main/misc/devel/tidy.pl#L168
            # Revert the substitutions done by the prettier plugin
            my $content = read_file($file);
            $content =~ s#<!--</head>-->#</head>#g;
            $content =~ s#<!--<body(.*)-->#<body$1#g;
            $content =~ s#<!--</body>-->#</body>#g;

(yes it's a bit messy, but our templates are messy)

Signed-off-by: Catherine Small <c.small@rcs.ac.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:33 -07:00
Martin Renvoize 856e6d3b38
Bug 39522: (follow-up) Remove debugging
Signed-off-by: Catherine Small <c.small@rcs.ac.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:33 -07:00
Martin Renvoize 522282cc83
Bug 39522: Add unit tests for valuebuilder plugin hooks
This patch adds comprehensive unit tests for the new valuebuilder plugin
functionality. The tests verify that plugins can provide valuebuilders
through the get_valuebuilder() method and that FrameworkPlugin can
correctly load and integrate with plugin-based valuebuilders.

The tests use a dedicated TestValuebuilder plugin to ensure realistic
testing scenarios without relying on mocks, following the pattern
established in other plugin hook tests.

Test plan:
1. Run the new test file:
   prove t/db_dependent/Koha/Plugins/Valuebuilder_hooks.t
2. Verify all tests pass (9 tests total)
3. Run all plugin tests to ensure no regressions:
   prove t/db_dependent/Koha/Plugins/
4. Verify all plugin tests still pass (113 tests across 19 files)
5. Test the specific functionality:
   - Verify plugins without get_valuebuilder() method are not found as valuebuilders
   - Verify plugins with get_valuebuilder() method are correctly discovered
   - Verify FrameworkPlugin can load plugin-based valuebuilders
   - Verify JavaScript generation works with plugin valuebuilders
   - Verify plugin enable/disable affects valuebuilder availability
6. Confirm FrameworkPlugin integration tests still pass:
   prove t/db_dependent/FrameworkPlugin.t

Signed-off-by: Catherine Small <c.small@rcs.ac.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:32 -07:00
Martin Renvoize 97fdd304ce
Bug 39522: Add filtering to the plugin home page for valuebuilders
This patch adds filtering functionality to the plugin home page to allow
valuebuilder plugins to be displayed alongside other plugins. This ensures
that valuebuilder plugins are properly integrated into the plugin management
interface and can be easily identified and managed by administrators.

Test plan:
1. Install a valuebuilder plugin (following the test plan from the
   previous patch)
2. Go to Administration > Plugins
3. Verify that the valuebuilder plugin appears in the plugins list
4. Confirm that the plugin shows the correct type/category information
5. Use any available filtering options to verify valuebuilder plugins
   can be filtered appropriately
6. Verify that valuebuilder plugins display the same management options
   (enable/disable/configure) as other plugin types
7. Test that plugin status changes are reflected correctly in the
   filtered view

Co-authored-by: Mark Hofstetter <mark@hofstetter.at>
Signed-off-by: Catherine Small <c.small@rcs.ac.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:32 -07:00
Jacob O'Mara 8e6b687f51
Bug 39522: Allow Valuebuilders to be installable as plugins
This patch adds hooks to allow 'Valuebuilder' plugins to be installable
using the standard plugin architecture for Koha. This enables cataloguing
plugins to be dynamically installed, enabled, and disabled through the
plugin system rather than being hardcoded framework components.

Test plan:
1. Install the example valuebuilder plugin from GitHub
   (https://github.com/Jacobomara901/koha-plugin-value-builder)
2. Go to Administration > Plugins
3. Verify the valuebuilder plugin appears in the plugins list
4. Enable/disable the plugin and confirm the custom valuebuilder
   appears/disappears in the framework configuration
5. Assign the custom valuebuilder to a MARC subfield in the
   bibliographic framework
6. Create/edit a bibliographic record and verify the valuebuilder
   button appears next to the configured subfield
7. Click the valuebuilder button and confirm it functions correctly
8. Disable the plugin and verify the valuebuilder button disappears
9. Re-enable the plugin and confirm functionality is restored
10. Uninstall the plugin and verify it's completely removed

Sponsored-by: Open Fifth <https://openfifth.co.uk>
Co-authored-by: Mark Hofstetter <koha@trust-box.at>
Signed-off-by: Catherine Small <c.small@rcs.ac.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:31 -07:00
Owen Leonard 77ee024313
Bug 41135: Convert About page Perl module table to DataTable
This patch updates the display of Perl modules on the About page,
showing them in a regular DataTable instead of a multi-column one. This
allows for sorting, filtering by required or missing, and searching.

To test, apply the patch and restart services.

- Go to the About page and click the "Perl modules" tab.
- The Perl modules should be displayed in a DataTable.
- Required modules are still shown in bold but now there is a column of
  with check marks for required modules so that you can sort or filter
  by required.
  - Test the "Show required" link at the top of the table and confirm
    that only required modules are shown.
- To test the display of Missing modules, uninstall at least one
  required module and at least one optional one, e.g.:

  > sudo dpkg --remove --force-depends libtest-mockmodule-perl libtest-mocktime-perl

- Restart services and return to the About page Perl modules list. Use
  the table filter form to search for "mock."
  - You should see the required module, Test::MockModule, with the
    status in red, "Required module missing."
  - The optional module, Test::MockTime, should have a yellow warning
    "Optional module missing."
- Clear the search form and click the "Show missing" link. The table
  should be filtered down to only those modules you removed.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:31 -07:00
Rudolf Byker a762e68e0d
Bug 37538: Improve documentation for the OCLC import daemon script
This updates the documentation for the
misc/bin/connexion_import_daemon.pl --help option.

Test plan:
1. Run ./misc/bin/connexion_import_daemon.pl --help
2. Read the output and check for errors, whether grammatical,
   spelling, or factual.

Sponsored-by: Reformational Study Centre <www.refstudycentre.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
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-01-12 10:53:30 -07:00
Lucas Gass d3c22bb552
Bug 40391: DRRev 25.12.00.007
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:30 -07:00
Martin Renvoize 76c2523b12
Bug 40391: Add support for GIR:LSL (Library Sub-Location) in EDIFACT processing
This implements support for the new EDItEUR/BiC GIR:LSL (Library Sub-Location)
field to complement the existing GIR:LSQ (Library Sequence) functionality,
allowing flexible mapping of both fields to location and collection item fields.

Changes:
- Add LSL mapping to GIR field extraction in Koha::Edifact::Line
- Update gir_segments() in Koha::Edifact::Order to handle both LSL and LSQ preferences independently
- Add validation functions in Koha::EDI using Koha::AuthorisedValues for location/collection codes
- Add EdifactLSL system preference for independent LSL field mapping
- Update EdifactLSQ to include empty option for ignore functionality
- Add admin interface configuration for LSL preference

The implementation provides maximum flexibility:
- EdifactLSQ can be set to: location, ccode, or empty (ignore)
- EdifactLSL can be set to: location, ccode, or empty (ignore)
- Both fields can be mapped independently or to same target field
- Validation ensures codes exist in LOC/CCODE authorised values
- Maintains full backwards compatibility with existing LSQ-only setups

Test plan:
1. Apply both patches
2. Update database: installer/data/mysql/updatedatabase.pl
3. Verify new system preferences in Administration > System preferences > Acquisitions:
   - EdifactLSQ: "Map EDI sequence code (GIR+LSQ) to Koha Item field"
   - EdifactLSL: "Map EDI sub-location code (GIR+LSL) to Koha Item field"
4. Create test authorised values:
   - LOC category: Add values like FICTION, REFERENCE
   - CCODE category: Add values like ADULT, CHILD
5. Set preferences to test combinations:
   - EdifactLSQ=location, EdifactLSL=ccode
   - EdifactLSQ=ccode, EdifactLSL=location
   - EdifactLSQ=location, EdifactLSL="" (empty)
6. Process EDIFACT messages containing GIR+LSL and GIR+LSQ segments
7. Verify items created with correct location/ccode values per preferences
8. Test invalid codes generate appropriate error messages
9. Confirm existing LSQ-only configurations continue working unchanged
10. Run: prove t/Edifact.t t/db_dependent/Koha/EDI.t t/db_dependent/Koha/Edifact/Order.t

Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/>
Sponsored-by: Royal Borough of Kensington and Chelsea <https://www.rbkc.gov.uk/>
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:29 -07:00
Martin Renvoize 14064e9459
Bug 40391: Add unit tests for GIR:LSL support in EDI
This adds unit tests for adding support for the new LSL field:

- GIR segment extraction tests in t/Edifact.t for
  sub_location_code field mapping
- Validation function tests in t/db_dependent/Koha/EDI.t
  for location/collection code validation
- Copy function tests in t/db_dependent/Koha/EDI.t for
  location/collection code assignment
- gir_segments generation tests in t/db_dependent/Koha/Edifact/Order.t
  for preference handling

Tests cover:
- LSL field extraction from GIR segments alongside existing LSQ fields
- Independent LSQ/LSL preference mapping (location/ccode/empty combinations)
- Validation of location and collection codes against authorised values
- Error handling and message formatting
- Backwards compatibility with existing LSQ-only configurations
- Edge cases including missing data and invalid preference values
- LSL maps to location, LSQ maps to ccode
- LSL maps to ccode, LSQ maps to location (swapped configuration)
- Only LSL field present (LSQ empty)
- Only LSQ field present (LSL empty)
- Both LSL and LSQ map to location (edge case)
- Both fields disabled

Test plan:
1. Apply this patch
2. Run: prove t/Edifact.t
   - Should pass with 8 new LSL-related tests included
3. Run: prove t/db_dependent/Koha/EDI.t
   - Should pass with 16 new validation function tests
4. Run: prove t/db_dependent/Koha/Edifact/Order.t
   - Should pass with 14 new gir_segments LSL/LSQ tests
5. Verify all existing tests still pass - no regression

Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/>
Sponsored-by: Royal Borough of Kensington and Chelsea <https://www.rbkc.gov.uk/>
Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:53:29 -07:00
Owen Leonard 1b1b9307b4
Bug 41142: Update jQuery-validate plugin to 1.21.0
This patch updates the jQuery validation plugin in both the staff
interface and the OPAC to the latest version, 1.21.0.

To test, apply the patch and clear your browser cache if necessary.

Test various pages in the staff client to confirm that form validation
works as expected:

 - Patron password change form:
   - Password must conform to minPasswordLength
   - Password must not contain leading/trailing spaces
   - Passwords must match

 - Administration -> Add or edit budget:
   - Description, start date, and end date are required
   - Start date must be before end date

 - Administration -> Add or edit Z39.50/SRU server:
   - Server name, hostname, port, and database are required
   - Port, rank, and timeout must be a number

Perform the same check of the "Change password" form in the OPAC.

Sponsored-by: Athens County Public Libraries
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-01-12 10:53:22 -07:00
Tomas Cohen Arazi e71ae37c95
Bug 32736: (follow-up) Make scripts using koha-worker use --all-queues
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:26:06 -07:00
Tomas Cohen Arazi dd11756ffc
Bug 32736: Add --all-queues switch to koha-worker
This patch adds a convenient option switch to the script. It will make
it perform the action (start, stop, restart, print status) on all
defined queues ('default' and 'long_tasks', as of today).

To test, make sure you applied bug 31124:

0. Apply this patch
1. Run:
   $ ktd --shell --root
  k$ cp debian/scripts/koha-functions.sh \
        /usr/share/koha/bin/koha-functions.sh
2. Play with all the options for:

  k$ debian/scripts/koha-worker * kohadev
=> SUCCESS: All options work as expected!

Note: you should include (say) having `long_tasks` stopped, and starting
all workers with the new parameter. And the same for `--stop` and
`--start`.

3. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:26:06 -07:00
Marcel de Rooy 73c0763f5b
Bug 41081: Remove spaces around 856$u in results and detail xslt
This patch does prevent creating useless links built on spaces like
http://%20%20%20%20 on the bug report.
A next step would be to stop preventing to create them when validating
during cataloguing.

Test plan:
Check without patch.
Create biblio with 856u having just a few spaces.
Verify that you see a useless Online resources link on OPAC or intranet
(detail and results).
Apply patch.
Restart all. Verify that you do not see the link anymore.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:26:05 -07:00
Lucas Gass dcaec7edaa
Bug 38876: DBRev 25.12.00.006
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:26:05 -07:00
Jessica Zairo e8dca2a842
Bug 38876: (follow-up) fix db rev
syntax was wrong for dbrev
To test apply patches
dbic
updatedatatbase
koha-mysql kohadev
select * from systempreferences where variable='UpdateNotForLoanStatusOnCheckout';
confirm the updated description

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:26:04 -07:00
Jessica Zairo eebb1595d7
Bug 38876: (QA follow-up) automicupdate
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:26:04 -07:00
Jessica Zairo fdcb5fd500
Bug 38876: Fixed typo in descrption of UpdateNotForLoanStatusOnCheckout
Test Plan
1. Before applying, navigate to administration->system preferences ->
   UpdateNotForLoanStatusOnCheckout
2. Observe the language in the description "When an item is checked out,
   if the not for loan value on the left matches the items not for loan
   value it will be updated to the right-hand value"
3. Apply the patch
4. Navigate to administration->system preferences ->
   UpdateNotForLoanStatusOnCheckout
5. Observe the language in the description has been updated to "When an
   item is checked out, if its not for loan value matches the value on
   the left, then the items not for loan value will be updated to the
   value on the right"
6. Do a reset all and confirm the language is correct

Signed-off-by: Andrew Auld <andrew.auld@openfifth.co.uk>
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:26:03 -07:00
Jonathan Druart 69c8a91cd9
Bug 41546: Restore the ability to unarchive suggestions
There is a TT condition that has not been moved to JS.

Test plan:
Create a suggestion, archive it
List suggestions and tick "include archived"
Notice that with this patch applied you are able to unarchive the
suggestion

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:26:03 -07:00
Hammat Wele 03fc6994a5
Bug 41102: Error 500 on the About page when biblioserver Zebra configuration is missing
When either the <listen id=biblioserver> entry or the
<server id=biblioserver listenref=biblioserver> section
is not present in koha-conf.xml, the Staff interface > About page
triggers a 500 Internal Server Error.

Test plan:
1. Edit koha-conf.xml and Comment out:
   <!--<listen id="biblioserver">unix:/path/to/zebradb/bibliosocket</listen>-->
2. Restart Apache: sudo systemctl restart apache2 (or restart_all)
3. Go to Intranet > About
   ==> Internal Server Error (500)
4. Now restore the <listen> line and instead comment out the entire
   <server id=biblioserver listenref=biblioserver>...</server> block.
   (Note that the commenting around <!-- The stuff below is used to enable SRU...
    interferes with adding a closing comment after </server> -
    close the comment before that section and add a comment around </server>.)
5. Repeat step 2, 3
   ==> Internal Server Error (500)
6. Apply the patch
7. Repeat step 2, 3
   ==> There is no Error (500)
       Note: Zebra status: Error message from Zebra: 10004
8. Uncomment <server id=biblioserver  listenref=biblioserver> </server>
   and Repeat Step 1
9. Repeat step 2, 3
   ==> There is no Error (500)
       Note: Zebra status: Zebra server seems not to be available. Is it started?

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-12 10:25:55 -07:00
Lucas Gass 8d69a341cf
Bug 41332: Fix file permissions for db_revs/251200004.pl
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 10:30:01 -07:00
Lucas Gass af1ec6b16a
Bug 35211: DBRev 25.12.00.005
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:57 -07:00
Lisette Scheer 97a022054d
Bug 35211: (QA follow-up) Fix counting
This patch updates detail.pl and detail.tt to correctly count the number of items for each tab.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:56 -07:00
Lisette Scheer c4c2a4b235
Bug 35211: (QA follow-up) fix display of other results tab and move template toolkit logic outside of script array
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:56 -07:00
Eric Garcia 23eb2dbd39
Bug 35211: Fixed group tab item building logic
Changed to show logged in branch in group tabs

Test Plan:
1. Apply patch, restart_all, updatedatabase
2. Koha Administration -> System preferences -> SeparateHoldingsByGroup
3. Change to "Separate holdings by library group in subsequent tabs..." and Save
4. Koha Administration -> Library Groups
5. Create multiple library groups and check the 'Use for staff search groups' box.
6. Add your logged in library to each group and other libraries to each group.
7. Do catalog searches where the location of the item is a library in a library group
8. Notice tabs in the Holdings table for each library group if it contains a library that has the item

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:55 -07:00
Eric Garcia f438535b5c
Bug 35211: Fix tab building
Fixed naming when building tabs

Test Plan:
1. Apply patch, restart_all, updatedatabase
2. Koha Administration -> System preferences -> SeparateHoldingsByGroup
3. Change to "Separate holdings by library group in subsequent tabs..." and Save
4. Koha Administration -> Library Groups
5. Create multiple library groups and check the 'Use for staff search groups' box.
6. Add your logged in library to each group and other libraries to each group.
7. Do catalog searches where the location of the item is a library in a library group
8. Notice tabs in the Holdings table for each library group if it contains a library that has the item

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:55 -07:00
Eric Garcia d2a0527d86
Bug 35211: Set SeparateHoldingsByGroup to separate based on SeparateHoldingsBranch syspref
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:54 -07:00
Eric Garcia 96c7eb3459
Bug 35211: Fixed count on logged in holdings
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:54 -07:00
Eric Garcia 124c6c8670
Bug 35211: Removed local items from group tabs and removed group items from other holdings tab
Fixed the issues with tabs.

I cannot replicate the issue in the attached screenshot. What steps did you take?

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:53 -07:00
Eric Garcia 5c2b0fd496
Bug 35211: Reordered tabs in Holdings Table and fixed group tabnames
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:53 -07:00
Eric Garcia 08045c8009
Bug 35211: Separate group holdings into individual tabs
Added SeparateHoldingsByGroup system preference
Adds new tabs to Holdings table if preference is enabled
Separates by library groups

Test Plan:
1. Apply patch, restart_all, updatedatabase
2. Koha Administration -> System preferences -> SeparateHoldingsByGroup
3. Change to "Separate holdings by library group in subsequent tabs..." and Save
4. Koha Administration -> Library Groups
5. Create multiple library groups and check the 'Use for staff search groups' box.
6. Add your logged in library to each group and other libraries to each group.
7. Do catalog searches where the location of the item is a library in a library group
8. Notice tabs in the Holdings table for each library group if it contains a library that has the item

Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:52 -07:00
Lucas Gass 39ca6bab0c
Bug 41332: DBRev 25.12.00.004
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:52 -07:00
Andreas Roussos 231a70ee45
Bug 41332: Add Greek to KohaManualLanguage system preference
The Koha Manual has been translated into Greek:
https://koha-community.org/manual/latest/el/html/index.html

However, 'Greek' is missing from the list of languages
available in the 'KohaManualLanguage' System Preference.

This patch fixes that.

Test plan:

1) Without the patch, visit Administration > System preferences >
   Enhanced content, and under the 'Manual' section notice that
   'Greek' is missing from the options of the 'KohaManualLanguage'
   syspref dropdown.
2) Apply the patch, and run updatedatabase.pl
   The 'KohaManualLanguage' dropdown should now list 'Greek'.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:51 -07:00
Lucas Gass 19209df1c0
Bug 39142: DBRev 25.12.00.003
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:51 -07:00
Lucas Gass 34a1463e90
Bug 39142: (RM follow-up) Add missing comma to userflags.sql
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:50 -07:00
Martin Renvoize 5d4c8d1dba
Bug 39142: Improve debug controls UI integration
Moves debug controls from fixed bottom position (which overlapped navbar)
to an integrated navbar dropdown with bug icon. Improves UX with:
- Proper navbar integration at far right after user menu
- Dynamic button text (Enable/Disable) reflecting current state
- Icon color change (amber) when any toggles are active
- Prevents duplicate URL parameters on multiple clicks

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:50 -07:00
Imani Thomas cc05dc9221
Bug 39142: fixing capitalization as per coding guidelines https://wiki.koha-community.org/wiki/Coding_Guidelines#HTML4:_Upper_and_lower_cases_in_strings
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:49 -07:00
Martin Renvoize 6744b12d3a
Bug 39142: (QA follow-up) Add permission checking for syspref overrides
This follow-up adds proper security controls for the DISABLE_SYSPREF_*
URL parameter functionality introduced in Bug 14004. Previously, any
user could bypass system preferences by using URL parameters without
authorization.

Changes:
- Restricts DISABLE_SYSPREF_* parameter processing to users with debug permission
- Uses existing haspermission() method for consistent authorization checking
- Maintains superlibrarian access as expected
- Closes potential security loophole where unauthorized users could disable
  system preferences like IntranetUserJS and IntranetUserCSS

The debug UI buttons will only appear for authorized users, and now the
underlying syspref override functionality requires the same permission,
ensuring both UI and functional security are aligned.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:49 -07:00
Martin Renvoize d1d1f05415
Bug 39142: (QA follow-up) Add unit tests for DISABLE_SYSPREF permission checking
This commit adds a unit test for the syspref override permission checking
functionality to ensure:

- Users without debug permission cannot override system preferences via URL parameters
- Users with debug permission can successfully override system preferences
- Superlibrarians maintain their ability to override system preferences
- The permission checking logic works correctly with haspermission()

The test creates three test patrons:
1. Regular patron with only catalogue permission (should be denied)
2. Patron with debug permission (should be allowed)
3. Superlibrarian (should be allowed)

Tests verify both the permission checking and the actual environment variable
setting that controls syspref overrides.

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:48 -07:00
Imani Thomas 315f9a95af
Bug 39142: Add debug permission to allow user to toggle JS and CSS customizations on/off
To test:
1. Apply the patch
2. Use updatedatabase run to update your db
3. Add custom JS and CSS in the IntranetUserJS and IntranetUserCSS fields
4. With a superLibrarian account confirm you have 3 buttons at the bottom of
   your screen, Disable User JS, Disable User CSS, and load only default
   stylesheets
5. Clicking the buttons should reload the current page with a query to
   disable those things.
6. Log in as a user that is not a superlibrarian and does not have the
   debug UI permission enabled
7. Confirm the buttons are no longer visible
8. Add the debug UI permission to the user
9. Confirm the debug buttons are now visible for that user.

Signed-off-by: Lari Strand <lari.strand@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:48 -07:00
Imani Thomas 9eef928712
Bug 39142: Add debug permission
This patch adds the database foundation for the debug permission feature:
- Creates bit 31 in userflags table for 'debug' permission
- Updates mandatory userflags.sql for fresh installations
- Provides atomic update script for existing installations

The debug permission will allow authorized users to access debugging
tools for toggling JavaScript and CSS customizations.

Signed-off-by: Lari Strand <lari.strand@koha-suomi.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 08:18:41 -07:00
Lucas Gass 94ba3d5344
Bug 39014: Remove unnecessary tests
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-09 06:42:45 -07:00
Lucas Gass fc1104c912
Bug 41329: Update yarn.lock
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 13:03:53 -07:00
Matt Blenkinsop f7a02a6164
Bug 41523: Improve the output from db rev 251200002
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:39 -07:00
Martin Renvoize 60d84f91e6
Bug 41520: Filter out record_table from AdditionalFieldValue API responses
Bug 35451 added a record_table field to the additional_field_values
database table for performance optimization (better joins). However,
this field was not added to the OpenAPI schema, and with
additionalProperties: false, the API validator rejects responses
containing it.

Rather than adding this internal implementation detail to the public
API schema, this patch adds a custom to_api method to
Koha::AdditionalFieldValue that filters out the record_table field.

This approach:
1. Keeps the database optimization from Bug 35451
2. Prevents internal implementation details from cluttering the API
3. Maintains context that API consumers already have from parent objects
4. Fixes OpenAPI validation errors for ERM agreements, licenses, and
   packages using extended_attributes

Test plan:
1. Create an ERM agreement with additional fields/extended attributes
2. Access the agreement via the API with extended_attributes embedded
3. Verify the response includes extended_attributes but not record_table
4. Confirm no OpenAPI validation warnings in plack-api-error.log
5. prove t/db_dependent/api/v1/erm_agreements.t
6. Verify all tests pass

Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:38 -07:00
Martin Renvoize c77b6b1a66
Bug 41520: Add tests for extended_attributes in ERM agreements API
This patch adds api unit tests for the extended_attributes functionality in the
ERM agreements API to ensure that:

1. Extended attributes are correctly returned when embedded
2. The internal record_table field is not exposed via the API
3. Both single GET and list endpoints work correctly with extended_attributes

Test plan:
1. prove t/db_dependent/api/v1/erm_agreements.t
2. Verify all tests pass

Signed-off-by: Jan Kissig <bibliothek@th-wildau.de>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:38 -07:00
David Nind ef2f7524c0
Bug 41494: Rename "Koha administration" module name to "Administration"
This changes "Koha administration" to "Administration" on the
staff interface home page and the administration module home page.

Everywhere else in the staff interface, such as browser page titles
and breadcrumbs, administration is used.

This improves consistency, and is how it is commonly referred to.

It also change some other occurences in the code base, such
as code comments.

Test plan:
1. Note the inconsistencies in how we name and refer to the
   administration module:
   1.1 Staff interface home page: Koha administration
   1.2 Administration module home page: Koha administration, but note
       that the breadcrumb and browser page title is "Administration".
   1.3 Breadcrumbs and browser page titles for any of the administration
       areas are just "Administration".
   1.4 More > Administration.
2. Apply the patch.
3. Note that on the staff interface home page and the administration
   home page are now labelled "Administration".

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Roman Dolny <roman.dolny@jezuici.pl>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:37 -07:00
Lucas Gass 6ea77ba89a
Bug 41484: Clarify strings in checkouts.js
To test:
1. APPLY PATCH, clear browser cache.
2. Turn on UseRecalls
3. Turn on EnableBookings, make the item type bookable via item type administration.
4. Check 3 things out to a paton.
5. Put one item on hold, recall one, and book one.
6. Look at the renew and checkin columns on the issues table. They should now read:

-Item on hold for another patron
-Item booked for another patron
-Item recalled by another patron

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:36 -07:00
Jonathan Druart f47ab213fd
Bug 41456: Rename more item_type => item_type_id in hold's api spec
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:36 -07:00
Tomás Cohen Arazi af4c4a33ed
Bug 41362: Allow Cypress tests to use KOHA_USER and KOHA_PASS as override
The cypress.config.ts file had hardcoded API credentials (koha:koha)
which don't work in KTD environments that use different credentials.

This patch makes the config use KOHA_USER and KOHA_PASS environment
variables if set, falling back to koha:koha if not.

Test plan:
1. On a running KTD, run the cypress tests:
   $ ktd --shell
  k$ run_cypress
=> SUCCESS: Tests are run, no errors about credentials
2. Edit your KTD .env file, so KOHA_USER=tcohen, KOHA_PASS=tomasito
3. Shut your kTD down, and start a fresh one
4. Repeat 1
=> SUCCESS: The command shows `cypress run --env KOHA_USER=tcohen,KOHA_PASS=tomasito` is run.
=> FAIL: Some tests fail right away with invalid credentials error
5. Apply this patch
6. Repeat 1
=> SUCCESS: Same command as 4
=> SUCCESS: Tests don't fail for credentials
7. Sign off :-D

Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:35 -07:00
Jonathan Druart fee1396744
Bug 41329: Add datatables.net@^1.13.1 jquery@^3.6.0
warning " > datatables.net-vue3@2.1.3" has unmet peer dependency "datatables.net@^1.13.1".
warning " > datatables.net-vue3@2.1.3" has unmet peer dependency "jquery@^3.6.0".

Test plan:
yarn build should not show those 2 warnings after this patch is applied

@RM: You will need to push yarn.lock as well

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:35 -07:00
Caroline Cyr La Rose 1decca5f54
Bug 41190: Add space in "Default checkout, hold and return policy" title
This patch adds a space in the title "Default checkout, hold and return
policy" in the circulation rules page.

To test:
1. Go to Administration > Circulation and fine rules
2. Select a library (e.g. Centerville)
3. Scroll down to the second table
   --> Note the title of the section is
   "Default checkout, hold and return policyfor Centerville" with a
   space missing between "policy" and "for"
4. Apply the patch
5. Refresh the page
   --> The title should now look normal with all the spaces!
   "Default checkout, hold and return policy for Centerville"

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:34 -07:00
Tomás Cohen Arazi 8b594db9c4
Bug 40446: Allow overriding Cypress DB settings
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>

Amended-by: Jonathan Druart
tidy

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:34 -07:00
Owen Leonard e856754c50
Bug 38739: (follow-up) Remove extra messages.inc include
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:33 -07:00
Jonathan Druart 2f5f801ad6
Bug 38739: Fix tools/batch_modify_holds.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:33 -07:00
Jonathan Druart 877e117e26
Bug 38739: Fix basket/basket.tt
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:32 -07:00
Jonathan Druart 03dd0759fe
Bug 38739: Fix some other wrong templates
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:32 -07:00
Jonathan Druart 7d8f11c3f1
Bug 38739: Fix admin/file_transports.pl
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:31 -07:00
Jonathan Druart 3a1836be81
Bug 38739: Fix mana-report-search-result.inc
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2026-01-06 07:45:23 -07:00
Lucas Gass ea9c10d4eb
Bug 41409: DBRev 25.12.00.002
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-31 07:09:34 -07:00
Aleisha Amohia b9953974fb
Bug 16131: Show branch transfer error messages in separate alert boxes
Currently error messages show in one alert box as bullet points. When
there is only one error shown, this looks a bit strange to have a list
with one item. This enhancement displays each error message as its own
alert box.

To test:

1. Check out a Centerville item to someone in Centerville. For example,
   check out 39999000004175 to yourself.
2. Go to Circulation -> Transfers. Enter the barcode 39999000004175 and
   transfer the item to Centerville.
3. Confirm two error messages are shown, with one blue box for each.

4. Go to Koha administration -> System preferences. Set
   UseBranchTransferLimits to Enforce and BranchTransferLimitsType to
   collection code.
5. Go to Koha administration -> Library transfer limits. Go to the REF
   tab and uncheck the Allow transfer? box for FFL - Fairfield, then hit
   Save
6. Go to Circulation -> Transfers. Enter the barcode 39999000004175 and
   transfer the item to Fairfield.
7. Confirm the error message shows in a blue box with no bullet point.
8. Type something random in the item barcode box and click Transfer.
9. Confirm the error message shows in a blue box with no bullet point.

Sponsored-by: Catalyst IT
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-30 07:49:07 -07:00
Alex Buckley d989883b82
Bug 35192: Highlight search terms in the OPAC Title notes tab
Test plan:
1. Create a biblio record with the word 'Fish' in the title (245$a) and
the summary (520$a)
2. Set 'OpacHighlightedWords' system preference to 'Highlight'
3. Submit an OPAC search with the search term of 'Fish'. Click on the biblio created in #1
4. Notice 'Fish' in the title is highlighted, as is the Summary. Scroll
down to the Holdings table. Click on 'Title notes' and notice 'Fish' is
not highlighted in the 'Title notes'
5. Apply patch and restart services
6. Refresh the biblio page. Click on the 'Title notes' tab again
7. Notice 'Fish' is highlighted in the 'Title notes' tab
8. Right click, click 'Inspect element'. Confirm there are no JavaScript
errors in the console.

Sponsored-by: Catalyst IT, New Zealand
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-30 07:49:07 -07:00
Matt Blenkinsop d3df2cce0f
Bug 41411: Remove the input length restriction on streetnumber
Test plan:
1) In the intranet, try to add a new patron
2) When typing into the streetnumber field, the string will be
   truncated after 10 characters
3) Try the same in the OPAC, the field will be limited
4) Repeat for the streetnumber for the alternate addresses
5) Apply patch
6) Repeat steps 1-4, the field should now accept a longer string

Sponsored-by Cheshire West and Chester Council

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-30 07:49:06 -07:00
Matt Blenkinsop 366ce3f08e
Bug 41409: Convert streetnumber and B_streetnumber to tinytext
Test plan:
1) Run 'updatedatabase'
2) No errors should be thrown

Sponsored-by: Cheshire West and Chester Council
Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-30 07:49:06 -07:00
Lari Taskula d214d441a8
Bug 39014: (follow-up) Refactoring and renaming to can_be_guaranteed_by
To test:
1. prove t/db_dependent/Koha/Patron.t

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>
2025-12-30 07:49:05 -07:00
Emmi Takkinen 382ba17a8c
Bug 39014: Add new method validate_guarantor
Adding a new guarantee and storing it fails with "Creating a patron"
selected in  TrackLastPatronActivityTriggers when guarantors are set
to be mandatory. The problem comes from Koha::Patron->store called
in update_lastseen. Since we validate guarantor in Koha::Patron->store
but don't store them in there, new call to store fails because it
doesn't receive guarantor info as parameter.

As a solution, we should validate guarantors in separated method.
Validation can't be moved e.g. to Koha::Patron::Relationship->store
since this could lead to situations where guarantee is stored without
guarantor. This patch adds new method validate_guarantor and adjusts
old guarantor requirements tests to match this new method.

To test:
1. Select option "Creating a patron" in "TrackLastPatronActivityTriggers" syspref.
2. Enable syspref "ChildNeedsGuarantor" and make sure you have options in
"borrowerRelationship" syspref.
3. Create a guarantee patron, fill all required fields and add guarantor for patron
(either add Patron guarantor or fill Non-patron guarantor inputs).
4. Attempt to save patron.
=> Error box is displayed with text "The following fields are wrong. Please fix them.".
=> Logs show error "Patron creation failed! - [Child patron needs a guarantor]".
5. Apply this patch.
6. Create a guarantee patron again and save.
=> Patron should be saved correctly.
7. Create a new guarantee patron but this time don't add patron guarantor or fill
non-patron guarantor inputs.
8. Attempt to save patron.
=> Save should fail with error "A child patron needs a guarantor."
9. Add patron guarantor who can also be a guarantee and save.
=> Save should fail with error "A guarantor cannot be a guarantee."

Also prove t/db_dependent/Koha/Patron.t

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Magnus Enger <magnus@libriotech.no>
Works as advertised.
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-30 07:48:56 -07:00
Jonathan Druart 66d92ba6c6
Bug 41456: Fix item type searching and filtering on the holds history table
Use should use coded_values for the library and item type column so that
we can search using the code and the description.

With this patch we now have a dropdown list with the libraries and item
types in the table's headers.

Test plan:
0. Turn on AllowHoldItemTypeSelection
1. Create a hold and select an item type
Note: I think there is a bug if you select it from "Hold next available
item", use "Hold details". Please confirm?
2. Go to the holds history view
3. Filter and search by item type description and code. Same for
   library.
   Use the dropdown list in the headers.

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>
2025-12-29 09:56:19 -07:00
Martin Renvoize 5810633b67
Bug 41404: (QA follow-up) Add tests for empty string and zero edge cases
This QA follow-up adds comprehensive test coverage for edge cases that
the original patch handles:

1. Empty string values ('') - the default value in sysprefs
   - Verifies that empty strings are treated as "not set"
   - Confirms charge calculations are skipped (performance optimization)
   - Ensures patrons are not blocked

2. Zero values (0) - valid numeric limit
   - Verifies that zero triggers charge calculation (valid number)
   - Confirms patrons are not blocked (0 means "no limit")
   - Documents intended behavior that 0 disables blocking

3. Positive numeric values - normal case
   - Confirms charges are calculated and blocking works correctly

These tests validate that looks_like_number() is the correct approach
rather than simple truthiness checking, as it properly distinguishes
between:
- Empty string '' (not a number, skip calculation)
- Zero 0 (is a number, calculate but don't block)

Test plan:
1. Apply this patch
2. Run the tests:
   prove t/db_dependent/Koha/Patron.t
3. Verify all tests pass

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:18 -07:00
Nick Clemens 154f807f6b
Bug 41404: Use looks_like_a_number to verify if prefs are set
This check adds a check to ensure we have number, not just defined

To test:
1 - Apply only unit tests patch
2 - Run the tests
    prove -v t/db_dependent/Koha/Patron.t
3 - Apply this patch
4 - Run the tests
5 - They pass!

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>
2025-12-29 09:56:18 -07:00
Lucas Gass e2943ae5e1
Bug 39482: (follow-up) Adjust tests
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:17 -07:00
Owen Leonard bfa29d0dc0
Bug 39482: Correct the link to edit OpacLibraryInfo from library edit page
When editing a library which has an associated OpacLibraryInfo HTML
customization the "Edit HTML content" link passes the wrong id
parameter.

To test, apply the patch and restart services.

- Rebuild OPAC CSS.
- Go to Administration -> Libraries.
- Edit a library.
- If there is no OpacLibraryInfo customization associated with that
  library, add at least two OpacLibraryInfo entries and then return to
  the library edit form.
- By the "OPAC information" label you should see all of the
  OpacLibraryInfo entries you added for that library.
- If you have permission to edit HTML customizations you should see
  an edit link.
- Test each to confirm that it opens the correct entry for editing.
- Return to Administration -> Libraries and click the library name to
  view it (rather than edit).
- The correct HTML customizations should appear. If you have permission
  to edit HTML customizations you should see corresponding "Edit" links.
- Test each to confirm that it opens the correct entry for editing.

- In the OPAC, locate a bibliographic record which has one or more items
  from the library you added OpacLibraryInfo to.
- In the holdings table, click the one of the library links to trigger
  the info modal.
- Confirm that all the correct information displays.
- Go to "Libraries" link in the menu under the quick search bar.
- Click the library you modified. Confirm that the correct
  OpacLibraryInfo entries display.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:16 -07:00
David Nind dca9a6e23c
Bug 41363: (follow-up) Add full stop at the end of the hint text
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:15 -07:00
Owen Leonard 096cc9088e
Bug 41363: Don't hide patron category limitation warning behind icon
This patch takes the warning text hidden behind an icon and puts
it into a "hint" <div> in order to make the information more accessible.

To test, apply the patch and go to Administration -> Patron categories.

- Edit a patron category to lmit it to a single library.
- Switch to that library and locate a patron in that category. Copy the
  card number.
- Switch to different library and search for the patron by card number.
- Edit the patron.
- Scroll down to the "Library management" section. Under the category
  dropdown you should see a "hint" style div with the library limitation
  warning.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:14 -07:00
Tomás Cohen Arazi af756eb19d
Bug 41339: Fix 'Too many checkout' typo
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:14 -07:00
Owen Leonard 18f4410734
Bug 41395: Terminology: Target item cannot be reserved from other branches
Testing Steps
1. Set the system preference CanReserveFromOtherBranches to “Don’t allow.”
2. Enable the system preference IndependentBranches.
3. Place a hold on an item from your local branch.
4. View the existing hold and select it.
5. Click “Move selected”, then choose “Record level holds to a different record.”
    * For testing, the original hold was placed on biblionumber 10 (Centerville item).
    * The hold was then moved to biblionumber 41 (Fairview item), which should not allow holds from another branch.
6. Observe the error message:
“Hold ID: 4 Target item cannot be reserved from other branches.”

7. Apply the patch and refresh the page.
8. Observe the updated error message:
“Hold ID: 4 Target item cannot be placed on hold from other libraries.”


Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:13 -07:00
Paul Derscheid f53159bfa4
Bug 41352: Add link to the manual for the Bookings to Collect page
To test:
- Go to Circulation > Bookings to collect
- Click Help
- Verify it links to the correct manual section

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:12 -07:00
Martin Renvoize eedb92ce36
Bug 41408: Added e.preventDefault() and e.stopPropagation() to edit
Prevent inline editing in POS for the cost and quantity fields from submitting the form on 'Enter'.

Test plan:
1. Ensure the POS page is accessible (Set UsePointOfSale, add a Cash
   Register and some Debit types for sale)
2. Navigate to the Point of Sale page (pos/pay.pl)
3. Add an item to the "This sale" table
4. Click on the "Cost" cell to edit the price inline
5. Change the value and press Enter
6. Expected: The cell value should update without submitting the form
7. Try editing the "Quantity" field as well (same fix applies)
8. Complete a normal transaction to ensure form submission still works with the submit button

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:11 -07:00
Owen Leonard be89df7470
Bug 41396: Capitalization: 'Transport Settings' and other
This patch fixes incorrect capitalization in the EDI accounts template.
See:
https://wiki.koha-community.org/wiki/Coding_Guidelines#HTML4:_Upper_and_lower_cases_in_strings

To test, apply the patch and go to Administration -> EDI accounts.

- Click "New account"
- The headings for each section of the form should have correct
  capitalization.

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:11 -07:00
Lucas Gass c827792838
Bug 41287: DBRev 25.12.00.001
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:10 -07:00
Martin Renvoize bdf5dc788c
Bug 41287: (QA follow-up) Fix HTML syntax and improve logic consistency
This patch addresses two issues found in QA review:

1. Fixed HTML syntax error in i18n_l10n.pref where closing anchor tag
   was incorrectly written as <a> instead of </a>

2. Changed C4/Search.pm to use elsif instead of separate if statement
   for FacetOrder checking, matching the logic structure used in
   Elasticsearch implementation and preventing potential double-execution

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-29 09:56:09 -07:00
David Nind 832f93f891
Bug 41287: (follow-up) Update system preference descriptions
Updated FacetSortingLocale description:
- added link to FacetOrder
- name of option for Facetorder matches option in dropdown list

Updated FacetOrder:
- changed preference to system preference

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>
2025-12-29 09:56:08 -07:00
Nick Clemens 66825efa06
Bug 41287: Add option to use simple Stringwise sorting for facets
This patch restores the previous perl sorting to restore speed. For English language libraries
this will be correct in most instances, and is much more performant.

To test:
1 - Apply patch, update database
2 - Perform some searches, note ordering of facets
3 - Update system preference 'FacetOrder' to 'simple alphabetical'
4 - Peform some searches, note ordering of facets, should be correct unless you have unicode characters
5 - Sign off

NOTE: Benchmarks attached as a comment, you can also enable NYTProf and view results for yourself:
https://wiki.koha-community.org/wiki/Profiling_with_Devel::NYTProf#NYTProf_with_Plack
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>
2025-12-29 09:56:07 -07:00
Jonathan Druart 6d491b2d95
Bug 41451: Prevent 500 when searching hold history
It explodes if the "Item type" column is displayed (ie. if
AllowHoldItemTypeSelection is set).

Test plan:
1) Create at least one hold on any record
2) Navigate to the patron who has the hold and click Holds history in the left menu
3) The table should load correctly and the search bar above it should work
4) In system preferences, enable AllowHoldItemTypeSelection
5) Refresh the holds history, the Requested item type column should now be visible
6) Try a search, a 500 error should be thrown
7) Apply patch
8) Repeat step 5, the search should now work correctly

Note that a couple of other bugs will be fixed on follow-up bug reports.

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>
2025-12-29 09:55:57 -07:00
Nick Clemens 7776239d6c
Bug 41432: Prefetch items and borrowers for holds
To test:
1 - Add a bunch of holds to a record
    https://github.com/kidclamp/handy-koha-script/blob/main/randhold.pl
2 - Enable profiling:
    https://wiki.koha-community.org/wiki/Profiling_with_Devel::NYTProf#NYTProf_with_Plack
3 - Browse to the record and load the holds table
4 - View the flame graph for the request like instructions in #2
5 - Note that Koha::Hold->borrower and Koha::Hold->item take a lot of time
6 - Apply patch, restart all
7 - Reload page
8 - Reload flame graph
9 - Note time for fetching borrowers and items is significantly reduced
10 - Sing off!

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:37 -07:00
Jonathan Druart 9f15e5911e
Bug 38345: Remove warnings
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>
2025-12-17 13:24:36 -07:00
Jonathan Druart afefb8a485
Bug 38345: Restore support for OpenSearch
The Koha_Main_OS1 and Koha_Main_OS2 jobs are currently failing on
Jenkins.

```
[NoNodes] ** No nodes are available: [http://es:9200], called from sub Search::Elasticsearch::Role::Client::Direct::__ANON__ at /kohadevbox/koha/Koha/SearchEngine/Elasticsearch/Indexer.pm line 417. # Looks like your test exited with 11 just after 3.
```

We need to workaround Elasticsearch/OpenSearch product check
incompatibility.

This patch suggests to monkey-patch
Search::Elasticsearch::Role::Cxn::process_response to disable the strict
product header check at runtime. This allows the same codebase to talk
to both Elasticsearch 8+ and OpenSearch without forking or patching
upstream.

Test plan:
0. Set KOHA_ELASTICSEARCH=yes in ktd's .env
1. Startup ktd with the compose file for OS1
 ktd -f docker-compose.os1.yml up
2. Set SearchEngine=Elasticsearch
3. reindex
 ktd --shell
 perl misc/search_tools/rebuild_elasticsearch.pl -a -b
4. run the tests
 prove -r t/db_dependent/Koha/SearchEngine/Elasticsearch
5. search!
http://localhost:8081/cgi-bin/koha/catalogue/search.pl?q=perl
should show results

6. ktd down then repeat using the compose file for OS2 (docker-compose.os2.yml), ES6 and ES7
for ES6 and ES7 you can use the --es6 and --es7 ktd flag.

IF you get a 403 during the reindex you can try to add
  cluster.routing.allocation.disk.threshold_enabled: "false"
to the environment section of the compose file.
Or
```
curl -X PUT http://es:9200/_cluster/settings \
  -H 'Content-Type: application/json' \
  -d '{
    "persistent": {
      "cluster.blocks.create_index": false
    }
  }'
```
OS thinks we lack disk space and force all indices into read-only.

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>
2025-12-17 13:24:36 -07:00
David Nind 052c93dbb5
Bug 41427: (QA follow-up) Add new line at end of file
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:35 -07:00
Caroline Cyr La Rose 5fbe31b52b
Bug 41427: Rephrase FilterSearchResultsByLoggedInBranch description
This patch rephrases the description of the
FilterSearchResultsByLoggedInBranch system preference to make it
clear it filters items by the library where the user is currently
logged in at. It also changes "branch" to "library" in the
system preference description.

To test:
1. Apply patch
2. Go to Administration > System preferences
3. Search for FilterSearchResultsByLoggedInBranch
4. Read the description, make sure it makes sense, and that the
   grammar and spelling are correct

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:35 -07:00
Martin Renvoize d1e82657f4
Bug 40219: Add unit test to verify welcome email is not sent on validation failure
This test ensures that when extended attribute validation fails during
patron creation via the API, no welcome email is sent. This validates
the fix where extended_attributes() validation was moved before the
welcome email is enqueued.

Test plan:
1. Apply this patch
2. Run: prove t/db_dependent/api/v1/patrons.t
3. Verify all tests pass, including the new test that confirms no welcome
   email is sent when extended attribute validation fails

Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:34 -07:00
Leo Stoyanov 5c66438cc3
Bug 40219: Moved Extended Attributes Validation Before Enqueuing Welcome Email
Test Plan:
1. Configure Koha with: AutoEmailNewUser = "Send" and create a mandatory extended attribute that will cause validation to fail; for example, set for the attribute to display in the OPAC, to be editable in the OPAC, and to be mandatory. That means this bug must be tested before Bug 40220, where the validation failure is resolved.
2. Setup your Postman, or any other mock API software, to obtain an OAuth token (/api/v1/oauth/token) and to register a patron (/api/v1/patrons).
3. With the OAuth token, send a POST request to /api/v1/patrons with valid patron data and invalid or missing mandatory extended attribute data.
4. Observe the API response with the 400 error and notice that the welcome email was delviered despite the fact that no patron account was created.
5. Apply the patch and repeat step 3. Notice that the API response still contains the 400 error, but no welcome email was sent.

Signed-off-by: Christopher Brannon <cbrannon@cdalibrary.org>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:34 -07:00
Paul Derscheid da8db7dcbb
Bug 40949: Remove second sentence of guidance text on Bookings to Collect page
- We don't need it since we don't have to trigger anything by checkin like we do with holds
- The first sentence already communicates all relevant information

Signed-off-by: Aude Charillon <aude.charillon@openfifth.co.uk>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:33 -07:00
Tomás Cohen Arazi 2224de5a5a
Bug 40989: Fix OAI-PMH tests for CGI.pm 4.68+
CGI.pm 4.68 (shipped with Debian 13/Trixie) changed the behavior of
self_url() to include a trailing slash when there's no path component.
This causes OAI-PMH tests to fail as they expect URLs without trailing
slashes.

This patch strips the trailing slash from both the requestURL field in
OAI responses and the baseURL field in Identify responses to maintain
compatibility across CGI.pm versions.

Test plan:
1. Start a 'trixie' KTD:
   $ KOHA_IMAGE=main-trixie ktd --proxy --name trixie up -d
   $ ktd --name trixie --wait-ready 120
2. Run the tests:
   $ ktd --name trixie --shell
  k$ prove t/db_dependent/OAI/Server.t
=> FAIL: Tests fail!
3. Apply this patch
5. Repeat 2
6. Repeat 1-2 with a 'bookworm' image
=> SUCCESS: Tests pass too!
7. Sign off :-D

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:33 -07:00
Pedro Amorim c597046a9e
Bug 40980: [ALTERNATIVE] Sanitize operators param
Ensure it does not contain 'cud-login'

Test plan, k-t-d with elastic search:
1) Make sure you're logged out
2) Access a search results page directly e.g.:
http://localhost:8081/cgi-bin/koha/catalogue/search.pl?q=music
3) You're prompted to login, login.
4) You're shown the search results directly, click a facet result (any facet)
5) You get a Error 403:
Programming error - op 'cud-login' must not start with 'cud-' for GET
6) Apply patch, restart plack, repeat

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:32 -07:00
Owen Leonard ed2556e7f3
Bug 40287: (follow-up) Use GetDescriptionByKohaField for item fields
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:32 -07:00
Owen Leonard 676b220687
Bug 40287: Fix untranslatable strings in more statistics wizards
This patch makes some updates to the acquisitions, patrons, and catalog
statistics wizards in order to make more information translatable.

To test apply the patch and go to Reports.

- Under "Statistics wizards," test the acquisitions, patrons,
  circulation, and catalog statistics reports:
  - Test setting some or all options in the "Filter column" and running
    the report.
  - At the top of the results there should be a list of the filter
    options you selected and their values.
  - All the filter labels should be correct, and the filter values
    should be in their full/description form rather than in code form
    (e.g. library, item type, collection code, sorting field, etc.)

Sponsored-by: Athens County Public Libraries
Signed-off-by: David Nind <david@davidnind.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:31 -07:00
Andrew Fuerste Henry fdc57489a1
Bug 41292: Rename whitelist to allowlist and add force_password_reset_when_set_by_staff
To test:
1 - Attempt to save a report containing "select force_password_reset_when_set_by_staff from categories"
2 - observe koha prevents this save
3 - apply patch, restart services
4 - repeat step 1
5 - observe your report now saves and runs successfully

While adding this new field to the list of allowed fields, I changed "whitelist" to "allowlist" as suggested by the terminology guide: https://wiki.koha-community.org/wiki/Terminology

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>
2025-12-17 13:24:31 -07:00
Kyle M Hall 1bb2532f33
Bug 41369: SIP payments have no branchcode
For SIP based payments, no branchcode is recorded for the corresponding
credit. Koha should be recording the institution id (i.e. branchcode)
for SIP payments.

Test plan:
1) Apply this patch
2) prove t/db_dependent/SIP/Transaction.t

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>
2025-12-17 13:24:30 -07:00
Nick Clemens 82561ff33d
Bug 41238: Update progress for peseudonmyzation bacgkround jobs
To test:
Enable pseudonymization, may need to add bcrypt settings (see bug 28911)
Perform some circulations
View the jobs (Admin->Manage jobs)
Filter for Pseudonymize statistic
Note the progress is 0/1 even when job finished
Apply patch, restart all
Do more circulations
Confirm jobs now say 1/1 complete

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>
2025-12-17 13:24:30 -07:00
Owen Leonard 247a43eca6
Bug 41361: Fix markup in category code confirmation modal
This patch fixes the markup in the modal, introduced by Bug 32581,
asking whether to update the patron's expiration date when changing the
patron category. The markup needs to be updated to conform with
Bootstrap 5.

To test, apply the patch and go to Patrons.

- Search for any patron and edit it.
- Scroll down to the "Library management" section.
- Change the category.
  - You should see a confirmation modal: "Confirm expiration date"
  - Test each of the controls to confirm that they work correctly: The
    "X" close button, the "Yes" and "No" buttons.

Sponsored-by: Athens County Public Libraries
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>
2025-12-17 13:24:29 -07:00
Jan Kissig e1f1c39e83
Bug 40455: SIP2 patron information fails when no currency is set
This fixes an error in SIP2 patron information response (64) when no currency is set, which can be the case on newly created instances.

Test plan for KTD:

a) Delete currency and related tables
 - run 'koha-mysql kohadev' to start SQL client. Then run the following queries:
 - delete from aqbasket;
   delete from aqbooksellers;
   delete from currency;
b) exit
c) run 'perl misc/sip_cli_emulator.pl -a 127.0.0.1 -p 6001 -su term1 -sp term1 -l CPL -m patron_information --patron 42'

  Attempting socket connection to 127.0.0.1:6001...connected!
  SEND: 9300CNterm1|COterm1|CPCPL|
  READ: 941

  Trying 'patron_information'
  SEND: 6300120251202    115652          AOCPL|AA42|ACterm1|
  READ: undef

d) recognize the last response: READ: undef
e) apply patch and restart sip2 server via 'sudo koha-sip --restart kohadev'
e) rerun 'perl misc/sip_cli_emulator.pl -a 127.0.0.1 -p 6001 -su term1 -sp term1 -l CPL -m patron_information --patron 42'

  Attempting socket connection to 127.0.0.1:6001...connected!
  SEND: 9300CNterm1|COterm1|CPCPL|
  READ: 941

  Trying 'patron_information'
  SEND: 6300120251202    122545          AOCPL|AA42|ACterm1|
  READ: 64              00120251202    122546000000000000000000000000AOCPL|AA42|AE koha|BLY|BV0|CC5|PCS|PIY|AFGreetings from Koha. |

f) check the last response is now a patron information response (64)

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>
2025-12-17 13:24:29 -07:00
Jonathan Druart 5a219c4083
Bug 40006: Remove dbh->do return value from updatedatabase output
There are 2 occurrences of "0E0" in the test suite output.

We should remove them.

Test plan:
root@kohadevbox:koha$ perl /kohadevbox/misc4dev/run_tests.pl --run-all-tests
And confirm that the 0E0 does no longer appear

You can abort when the update DB process is done

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>
2025-12-17 13:24:28 -07:00
Owen Leonard 971028cf3a
Bug 40567: Correct eslint errors in recalls.js
This patch corrects a few minor errors in recalls.js. There should be no
change to how the pages work.

The following functions have been changed:

- Cancel a recall from the recalls tab on a patron record
- Expire a recall or revert the waiting status of a recall from the
  "Recalls awaiting pickup" page.
- Cancel a recall which has been transferred from the "Recalls queue"
  page.
- Mark a recall "Overdue" from the "Recalls queue" page.
- Cancel selected recalls on the "Recalls queue" page.

Sponsored-by: Athens County Public Libraries
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>
2025-12-17 13:24:28 -07:00
Owen Leonard e0bef69d47
Bug 40619: Remove OverDrive star ratings from the OPAC
Since OverDrive no longer supplies star ratings through their API we can
remove the corresponding JS from the template.

To test, apply the patch and enable OverDrive integration.

- In the OPAC, perform a search which will return results from your
  OverDrive collection.
- Follow the link a the top of Koha search results, "Found X results in
  the library's OverDrive collection"
- Confirm that OverDrive search results load correctly and that nothing
  looks broken.

Sponsored-by: Athens County Public Libraries
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:27 -07:00
Nick Clemens 7b1d11da74
Bug 39514: Only mark the affected rows as problematic
Before this patch we were setting a variable and nebver unsetting it

Now we can add the conditional directly to the row, so don't need a variable

To test:
1. Go to Acquisitions
2. Click 'Search' in the vendor search
3. Click 'Add to basket' next to 'My basket'
4. Click 'From a new (empty) record'
5. Fill out the form
   - Title: enter a title
   - Koha item type: choose an item type
   5.1. Click 'Add item'
   - Fund: choose a fund
   - Uncertain price: check
6. Click 'Save'
7. Go back to Acquisitions
8. Click 'Search' in the vendor search
9. Click 'New' > 'Basket'
10. Enter a basket name
11. Click 'Save'
12. Go back to Acquistions
13. Click 'Search' in the vendor search
--> Notice the empty basket is also in bold red, even if it doesn't have uncertain prices
14. Click 'Add to basket'
--> Notice everything is in bold and some in red
15. Apply patch, reload
16. Only affected basket is in red

Signed-off-by: Samuel <sam.sowanick@corvallisoregon.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:27 -07:00
Owen Leonard 6eabfe0d50
Bug 41348: Capitalization: "List Files" and others
This patch corrects two strings in js/transport_status.js so that it has
correct capitalization.

I'm not sure how to test every way these strings are seen in the
interface, but checking the patch should show only capitalization
changes.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:26 -07:00
Alex Buckley e027ed2dca
Bug 41145: Add unit test coverage for stopping duplicated borrowerslog logging due to extended patron attributes being deleted on import
This adds to the unit tests in t/db_dependent/Koha/Patrons/Import.t. It confirms that repeating the same patron import (with extended patron attributes) will not generate duplicated borrowersLog entries.

Test plan:
1. prove t/db_dependent/Koha/Patrons/Import.t

Sponsored-by: Auckland University of Technology
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>
2025-12-17 13:24:26 -07:00
Alex Buckley 2608f48952
Bug 41145: Do not delete the existing extended_attribute values when importing patrons
Since bug 26744 was upstreamed when you use the Import patron tool or the import_patrons.pl script from the commandline and pass a preserve-extended-attributes parameter the borrowerlogs inaccurately have an entry added where the attribute 'before' value is empty.

Test plan:
1. Create a patron attribute type named 'INSTID': Koha administration > Patrons and circulation > Patron attribute types > New patron attribute type.
2. Download the patron started CSV from: Tools > Import patrons > click the 'Download a Starter CSV file with all the columns.' link
3. Save values in the: cardnumber, surname, firstname, branchcode, categorycode, auth_method, lang, and patron_attributes columns
4. Make sure your value in the patron_attributes column is INSTID:1234
5. Import this csv file:
cd misc
./import_patrons.pl --file <filename>.csv --matchpoint cardnumber --default branchcode=CPL --overwrite --preserve-extended-attributes --verbose --verbose --confirm

6. Navigate to the patron account in your browser
7. Click 'Modification log' and observe how many entries there are
8. Repeat step 5
9. Notice there is a new entry in your Modification log with an info column value of: { "attribute.INSTID" : { "after" : "1234", "before" : "" } }
10. Repeat step 5 and notice another new entry in the Modification log with an empty "before" value

11. Apply patch
12. Repeat step 5 and notice no new entry in the Modification log
13. Edit your csv and change the patron_attributes value to: INSTID:5678
14. Repeat step 5 and notice the new modification log entry with an info column value of: { "attribute.INSTID" : { "after" : "5678", "before" : "1234" } }

15. Add another new patron attribute type to your Koha named 'home'
16. edit your csv and change the patron_attributes value to: INSTID:5678,home:town
17. Repeat step 5
18. Notice a new Modification log entry with the value of: { "attribute.home" : { "after" : "town", "before" : "" } }

Sponsored-by: Auckland University of Technology
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>
2025-12-17 13:24:25 -07:00
Andrew Fuerste Henry 994f091a32
Bug 41345: Restore ignore button functionality
To test:
1 - place a hold
2 - check an item in to fill your hold, confirm so item goes to Waiting status
3 - check item in again, get Hold Already Waiting modal
4 - clck Ignore, confirm modal reloads immediately
5 - apply patch, restart_all
6 - reload checkin page
7 - check item in again, get Hold Already Waiting modal
8 - click Ignore, confirm modal goes away and doesn't return

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:25 -07:00
Marcel de Rooy 1ed55841cd
Bug 37796: (QA follow-up) nextpublisheddate in t/db../GetNextSeq.t
Just adding the fifth parameter here, since that actually should
be the next published date. But note that it is just a placeholder,
since the test does not use the associated date substitutions.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:24 -07:00
Nick Clemens 7c4f8d00c2
Bug 37796: (follow-up) Correct call in showpredictionpattern.pl
Signed-off-by: Wesley Owen <wesleyowen@churchofjesuschrist.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:24 -07:00
Nick Clemens f2f30ad9b9
Bug 37796: Pass correct publication date when generating the next serial form the serial collection page
Before this patch, we pass the previous issue date to generate the next seq - this works for the pattern as we are incrementing.

When the patter is translated ot words, we need to do this from the date of the next issue, so we must pass in the next
published date

This patch changes the call to GetNextSeq from serial-collection.pl to ensure the new issue publication date is used to
generate the numbers.

To test:
0 - Follow the plan to recreate from the initial bug description and recreate
1 - Apply patch
2 - On the serial used in previous patch go to 'Serial collection'
3 - Generate next issue
4 - Confirm the numbering is correctly generated after this patch

Signed-off-by: Wesley Owen <wesleyowen@churchofjesuschrist.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-17 13:24:17 -07:00
Martin Renvoize 7abf1aeef9
Bug 41317: Add 25.11 maintainers
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-16 06:44:46 -07:00
Martin Renvoize 1f00b37ae1
Bug 41317: Add 26.05 Team
This adds the 26.05 release team as proposed.

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>
2025-12-16 06:44:45 -07:00
Martin Renvoize d4ade572ae
Bug 41317: Corrections for 25.11 cycle
Update the teams list to reflect the reality of the 25.11 cycle with an
updated release date and updates to the QA team to reflect those who
performed duties during the cycle.

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>
2025-12-16 06:44:38 -07:00
Lucas Gass 087dccae8c
DBRev 25.12.00.000: Start of a new release cycle
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-15 10:32:33 -07:00
Lucas Gass 149e791bfa
Bug 41421: Fix DB update
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
2025-12-10 11:08:10 -07:00
2215 changed files with 241207 additions and 51217 deletions

View File

@ -1,7 +1,6 @@
ser
thes
nam
rovider
fo
isnt
wither
@ -18,3 +17,12 @@ theses
rouge
connexion
falsy
aas
carmel
checkin
laf
lamba
plack
prfile
requestor
vai

3
.gitignore vendored
View File

@ -13,12 +13,15 @@ koha-tmpl/opac-tmpl/bootstrap/css/print.css
koha-tmpl/opac-tmpl/bootstrap/css/print-rtl.css
koha-tmpl/opac-tmpl/bootstrap/css/sco.css
koha-tmpl/opac-tmpl/bootstrap/css/sco-rtl.css
koha-tmpl/opac-tmpl/bootstrap/js/vue/dist/
koha-tmpl/intranet-tmpl/prog/css/maps/
koha-tmpl/intranet-tmpl/prog/css/bookings.css
koha-tmpl/intranet-tmpl/prog/css/bookings-rtl.css
koha-tmpl/intranet-tmpl/prog/css/calendar.css
koha-tmpl/intranet-tmpl/prog/css/calendar-rtl.css
koha-tmpl/intranet-tmpl/prog/css/edifact_interchange.css
koha-tmpl/intranet-tmpl/prog/css/edifact_interchange-rtl.css
koha-tmpl/intranet-tmpl/prog/css/holds.css
koha-tmpl/intranet-tmpl/prog/css/holds-rtl.css
koha-tmpl/intranet-tmpl/prog/css/ill-requests.css

View File

@ -230,6 +230,8 @@ Lari Strand <lmstrand@gmail.com> <lmstrand@gmail.
Larry Baerveldt <larry@bywatersolutions.com>
Laura Escamilla <laura.escamilla@bywatersolutions.com>
Laurence Rault <laurence.rault@biblibre.com> <laurence.rault@biblibre.com>
Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org> <loreganlloyd@clamsnet.org>
Lawrence O'Regan-Lloyd <loreganlloyd@clamsnet.org> <hello@lawrenceol.dev>
Leire Diez <leire@scanbit.net>
Liz Rea <liz@catalyst.net.nz>
Liz Rea <lrea@nekls.org> <liz@nekls.org>

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
20

View File

@ -28,12 +28,14 @@ BEGIN {
}
use C4::Context;
use C4::Stats;
use C4::Members;
use Koha::Account;
use Koha::Account::Lines;
use Koha::Account::Offsets;
use Koha::CirculationRules;
use Koha::Checkouts;
use Koha::Items;
use Koha::Patrons;
=head1 NAME
@ -65,39 +67,70 @@ FIXME : if no replacement price, borrower just doesn't get charged?
sub chargelostitem {
my $dbh = C4::Context->dbh();
my ( $borrowernumber, $itemnumber, $replacementprice, $description ) = @_;
my $item = Koha::Items->find($itemnumber);
my $itype = $item->itemtype;
my ( $borrowernumber, $itemnumber, $replacementprice, $description, $opts ) = @_;
$opts ||= {};
my $patron = Koha::Patrons->find($borrowernumber);
my $item = Koha::Items->find($itemnumber);
my $itype = $item->itemtype;
$replacementprice //= 0;
my $defaultreplacecost = $itype->defaultreplacecost;
my $processfee = $itype->processfee;
my $usedefaultreplacementcost = C4::Context->preference("useDefaultReplacementCost");
my $processingfeenote = C4::Context->preference("ProcessingFeeNote");
my $lost_control_pref = C4::Context->preference('LostChargesControl');
my $lost_control_branch;
if ( defined $lost_control_pref && $lost_control_pref eq 'PatronLibrary' ) {
$lost_control_branch = $patron->branchcode;
} elsif ( defined $lost_control_pref && $lost_control_pref eq 'PickupLibrary' ) {
$lost_control_branch = C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef;
} else { # $lost_control_pref eq 'ItemHomeLibrary' or undefined
$lost_control_branch =
( C4::Context->preference('HomeOrHoldingBranch') eq 'homebranch' )
? $item->homebranch
: $item->holdingbranch;
}
my $interface = $opts->{interface} // C4::Context->interface;
my $library_id = defined $opts->{library_id} ? $opts->{library_id} : $lost_control_branch;
my $issue_id = $opts->{issue_id};
if ( !defined $issue_id ) {
my $checkout = Koha::Checkouts->find( { itemnumber => $itemnumber } );
if ( !$checkout && $item->in_bundle ) {
my $host = $item->bundle_host;
$checkout = $host->checkout;
}
$issue_id = $checkout ? $checkout->issue_id : undef;
}
if ( $usedefaultreplacementcost && $replacementprice == 0 && $defaultreplacecost ) {
$replacementprice = $defaultreplacecost;
}
my $checkout = Koha::Checkouts->find( { itemnumber => $itemnumber } );
if ( !$checkout && $item->in_bundle ) {
my $host = $item->bundle_host;
$checkout = $host->checkout;
}
my $issue_id = $checkout ? $checkout->issue_id : undef;
my $account = Koha::Account->new( { patron_id => $borrowernumber } );
# first make sure the borrower hasn't already been charged for this item (for this issuance)
my $existing_charges = $account->lines->search(
{
itemnumber => $itemnumber,
debit_type_code => 'LOST',
issue_id => $issue_id
issue_id => $issue_id,
}
)->count();
# OK, they haven't
unless ($existing_charges) {
my $processfee = Koha::CirculationRules->get_effective_rule_value(
{
rule_name => "lost_item_processing_fee",
categorycode => undef,
itemtype => $itype->itemtype,
branchcode => $library_id,
}
) // 0;
#add processing fee
if ( $processfee && $processfee > 0 ) {
my $accountline = $account->add_debit(
@ -106,11 +139,11 @@ sub chargelostitem {
description => $description,
note => $processingfeenote,
user_id => C4::Context->userenv ? C4::Context->userenv->{'number'} : undef,
interface => C4::Context->interface,
library_id => C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef,
interface => $interface,
library_id => $library_id,
type => 'PROCESSING',
item_id => $itemnumber,
issue_id => $issue_id,
( defined $issue_id ? ( issue_id => $issue_id ) : () ),
}
);
}
@ -123,11 +156,11 @@ sub chargelostitem {
description => $description,
note => undef,
user_id => C4::Context->userenv ? C4::Context->userenv->{'number'} : undef,
interface => C4::Context->interface,
library_id => C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef,
interface => $interface,
library_id => $library_id,
type => 'LOST',
item_id => $itemnumber,
issue_id => $issue_id,
( defined $issue_id ? ( issue_id => $issue_id ) : () ),
}
);
}

View File

@ -69,7 +69,7 @@ BEGIN {
use Carp qw( carp croak );
use Text::CSV_XS;
use C4::Context;
use C4::Suggestions qw( GetSuggestion GetSuggestionFromBiblionumber ModSuggestion );
use C4::Suggestions qw( ModSuggestion );
use C4::Biblio qw( GetMarcFromKohaField GetMarcStructure IsMarcStructureInternal );
use C4::Contract qw( GetContract );
use C4::Log qw( logaction );
@ -1417,11 +1417,11 @@ sub ModReceiveOrder {
$order->{invoice_unitprice} ||= $order->{unitprice};
$order->{invoice_currency} ||= Koha::Acquisition::Currencies->get_active->currency;
my $suggestionid = GetSuggestionFromBiblionumber($biblionumber);
if ($suggestionid) {
my $suggestion = Koha::Suggestions->search( { biblionumber => $biblionumber } )->single;
if ($suggestion) {
ModSuggestion(
{
suggestionid => $suggestionid,
suggestionid => $suggestion->suggestionid,
STATUS => 'AVAILABLE',
biblionumber => $biblionumber
}
@ -2418,6 +2418,7 @@ sub GetInvoices {
my $query = qq{
SELECT aqinvoices.invoiceid, aqinvoices.invoicenumber, aqinvoices.booksellerid, aqinvoices.shipmentdate, aqinvoices.billingdate, aqinvoices.closedate, aqinvoices.shipmentcost, aqinvoices.shipmentcost_budgetid, aqinvoices.message_id,
aqbooksellers.name AS suppliername,
aqbooksellers.invoiceincgst,
COUNT(
DISTINCT IF(
aqorders.datereceived IS NOT NULL,
@ -2432,7 +2433,20 @@ sub GetInvoices {
NULL
)
) AS is_linked_to_subscriptions,
SUM(aqorders.quantityreceived) AS receiveditems
SUM(aqorders.quantityreceived) AS receiveditems,
SUM(aqorders.quantity) AS itemsexpected,
COALESCE(
(SELECT SUM(o.unitprice_tax_excluded * o.quantity) FROM aqorders o WHERE o.invoiceid = aqinvoices.invoiceid),
0
) AS total_tax_excluded,
COALESCE(
(SELECT SUM(o.unitprice_tax_included * o.quantity) FROM aqorders o WHERE o.invoiceid = aqinvoices.invoiceid),
0
) AS total_tax_included,
COALESCE(
(SELECT SUM(adj.adjustment) FROM aqinvoice_adjustments adj WHERE adj.invoiceid = aqinvoices.invoiceid),
0
) AS total_adj
FROM aqinvoices
LEFT JOIN aqbooksellers ON aqbooksellers.id = aqinvoices.booksellerid
LEFT JOIN aqorders ON aqorders.invoiceid = aqinvoices.invoiceid
@ -2525,12 +2539,12 @@ sub GetInvoices {
}
$query .=
" GROUP BY aqinvoices.invoiceid, aqinvoices.invoicenumber, aqinvoices.booksellerid, aqinvoices.shipmentdate, aqinvoices.billingdate, aqinvoices.closedate, aqinvoices.shipmentcost, aqinvoices.shipmentcost_budgetid, aqinvoices.message_id, aqbooksellers.name";
" GROUP BY aqinvoices.invoiceid, aqinvoices.invoicenumber, aqinvoices.booksellerid, aqinvoices.shipmentdate, aqinvoices.billingdate, aqinvoices.closedate, aqinvoices.shipmentcost, aqinvoices.shipmentcost_budgetid, aqinvoices.message_id, aqbooksellers.name, aqbooksellers.invoiceincgst";
if ( $args{order_by} ) {
my ( $column, $direction ) = split / /, $args{order_by};
if ( grep { $_ eq $column } @columns ) {
$direction ||= 'ASC';
$direction = ( uc( $direction // '' ) eq 'DESC' ) ? 'DESC' : 'ASC';
$query .= " ORDER BY $column $direction";
}
}

View File

@ -194,8 +194,9 @@ sub get_template_and_user {
$in->{query}->param( -name => 'login_password', -values => [$AutoSelfCheckPass] );
$in->{query}->param( -name => 'koha_login_context', -values => ['sco'] );
} else {
my $request_method = $in->{query}->request_method // q{};
unless ( $request_method eq 'POST' && $in->{query}->param('op') eq 'cud-login' ) {
my $request_method = $in->{query}->request_method // q{};
my $login_op = $in->{query}->param('login_op') // q{};
unless ( $request_method eq 'POST' && $login_op eq 'cud-login' ) {
for my $v (qw( login_userid login_password )) {
$in->{query}->param( $v, '' )
if $in->{query}->param($v);
@ -274,15 +275,18 @@ sub get_template_and_user {
}
if ($kick_out) {
$session = get_session("");
$template = C4::Templates::gettemplate(
'opac-auth.tt', 'opac',
$in->{query}
);
$template->param( sessionID => $session->id );
$cookie = $cookie_mgr->replace_in_list(
$cookie,
$in->{query}->cookie(
-name => 'CGISESSID',
-value => '',
-value => $session->id,
-HttpOnly => 1,
-secure => ( C4::Context->https_enabled() ? 1 : 0 ),
-sameSite => 'Lax',
@ -444,21 +448,24 @@ sub get_template_and_user {
# Sysprefs disabled via URL param
# Note that value must be defined in order to override via ENV
foreach my $syspref (
qw(
OPACUserCSS
OPACUserJS
IntranetUserCSS
IntranetUserJS
OpacAdditionalStylesheet
opaclayoutstylesheet
intranetcolorstylesheet
intranetstylesheet
)
)
{
$ENV{"OVERRIDE_SYSPREF_$syspref"} = q{}
if $in->{'query'}->param("DISABLE_SYSPREF_$syspref");
# Only allow syspref overrides for users with debug permission
if ( C4::Context->userenv && haspermission( C4::Context->userenv->{'id'}, { debug => 1 } ) ) {
foreach my $syspref (
qw(
OPACUserCSS
OPACUserJS
IntranetUserCSS
IntranetUserJS
OpacAdditionalStylesheet
opaclayoutstylesheet
intranetcolorstylesheet
intranetstylesheet
)
)
{
$ENV{"OVERRIDE_SYSPREF_$syspref"} = q{}
if $in->{'query'}->param("DISABLE_SYSPREF_$syspref");
}
}
# Anonymous opac search history
@ -485,9 +492,6 @@ sub get_template_and_user {
GoogleJackets => C4::Context->preference("GoogleJackets"),
OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
LoginFirstname => ( C4::Context->userenv ? C4::Context->userenv->{"firstname"} : "Bel" ),
LoginSurname => C4::Context->userenv ? C4::Context->userenv->{"surname"} : "Inconnu",
emailaddress => C4::Context->userenv ? C4::Context->userenv->{"emailaddress"} : undef,
TagsEnabled => C4::Context->preference("TagsEnabled"),
hide_marc => C4::Context->preference("hide_marc"),
item_level_itypes => C4::Context->preference('item-level_itypes'),
@ -741,6 +745,18 @@ has authenticated.
=cut
=head2 _version_check
_version_check( $type, $query );
Checks whether the database version matches the code version. If the C<Version>
system preference is missing (fresh install) or outdated, redirects to the web
installer (staff) or the maintenance page (OPAC) and exits.
C<$type> is C<"opac"> or C<"intranet">. C<$query> is the current CGI object.
=cut
sub _version_check {
my $type = shift;
my $query = shift;
@ -787,6 +803,17 @@ sub _version_check {
}
}
=head2 _timeout_syspref
my $timeout = _timeout_syspref();
Reads the C<timeout> system preference and returns the session timeout value in
seconds. Understands plain integer values (already seconds), C<d> (days), and
C<h> (hours) suffixes. Falls back to 600 seconds if the preference is unset or
contains an unrecognised value.
=cut
sub _timeout_syspref {
my $default_timeout = 600;
my $timeout = C4::Context->preference('timeout') || $default_timeout;
@ -1236,7 +1263,7 @@ sub checkauth {
: Koha::Cash::Registers->search(
{ branch => $branchcode, branch_default => 1 },
{ rows => 1 }
)->single;
)->single;
$register_id = $register->id if ($register);
$register_name = $register->name if ($register);
}
@ -1415,6 +1442,7 @@ sub checkauth {
$uri->query_param_delete('login_userid');
$uri->query_param_delete('login_password');
$uri->query_param_delete('koha_login_context');
$uri->query_param_delete('login_op');
$uri->query_param_delete('op');
$uri->query_param_delete('csrf_token');
unless ( $params->{do_not_print} ) {
@ -1942,6 +1970,16 @@ will be created.
=cut
=head2 _get_session_params
my $params = _get_session_params();
Returns a hashref of parameters used to initialise a CGI::Session object
(driver, datasource, and related options). Delegates to
C<Koha::Session-E<gt>_get_session_params>. Kept for backwards compatibility.
=cut
#NOTE: We're keeping this for backwards compatibility
sub _get_session_params {
return Koha::Session->_get_session_params();
@ -2113,23 +2151,12 @@ Missing POD for checkpw_internal.
=cut
sub checkpw_internal {
my ( $userid, $password, $no_set_userenv ) = @_;
my ( $identifier, $password, $no_set_userenv ) = @_;
$password = Encode::encode( 'UTF-8', $password )
if Encode::is_utf8($password);
my $patron = Koha::Patrons->find( { userid => $userid } );
if ($patron) {
if ( checkpw_hash( $password, $patron->password ) ) {
my $borrowernumber = $patron->borrowernumber;
C4::Context->set_userenv(
"$borrowernumber", $patron->userid, $patron->cardnumber,
$patron->firstname, $patron->surname, $patron->branchcode, $patron->library->branchname, $patron->flags
) unless $no_set_userenv;
return 1, $patron->cardnumber, $patron->userid, $patron;
}
}
$patron = Koha::Patrons->find( { cardnumber => $userid } );
my $patron = Koha::Patrons->find_by_identifier($identifier);
if ($patron) {
if ( checkpw_hash( $password, $patron->password ) ) {
my $borrowernumber = $patron->borrowernumber;
@ -2338,6 +2365,17 @@ Returns member's flags or 0 if a permission is not met.
=cut
=head2 _dispatch
my $ok = _dispatch( $required, $flags );
Recursively evaluates a permission requirement structure against the flags held
by a patron. C<$required> may be a scalar permission name, an arrayref (OR
semantics), or a hashref (AND semantics). C<'*'> matches any non-false value.
Returns 1 if the requirement is satisfied, 0 otherwise.
=cut
sub _dispatch {
my ( $required, $flags ) = @_;

View File

@ -312,7 +312,7 @@ Create some metadata for your koha instance (if you're in a single instance setu
=item 2.
Swap metadata with your Identidy Provider (IdP)
Swap metadata with your Identify Provider (IdP)
=item 3.
@ -365,7 +365,7 @@ Note: The minimum you need here is a <matchpoint> block, containing a valid colu
It should be as simple as that; you should now be able to login via shibboleth in the opac.
If you need more help configuring your B<S>ervice B<P>rovider to authenticate against a chosen B<Id>entity B<P>rovider then it might be worth taking a look at the community wiki L<page|https://wiki.koha-community.org/wiki/Shibboleth_Configuration>
If you need more help configuring your B<S>ervice B<P>rovider to authenticate against a chosen B<Id>entity B<P>rovider then it might be worth taking a look at the community wiki L<page|https://wiki.koha-community.org/wiki/Shibboleth_Configuration> # codespell:ignore
=head1 FUNCTIONS

View File

@ -53,6 +53,7 @@ BEGIN {
}
use MARC::Field;
use MARC::Record::MiJ;
use Scalar::Util qw(blessed);
use Try::Tiny qw( try catch );
@ -107,18 +108,25 @@ sub GetAuthMARCFromKohaField {
=head2 SearchAuthorities
(\@finalresult, $nbresults)= &SearchAuthorities($tags, $and_or,
$excluding, $operator, $value, $offset,$length,$authtypecode,
$sortby[, $skipmetadata])
( \@finalresult, $nbresults ) = SearchAuthorities(
$tags, $and_or,
$excluding, $operator, $value, $offset, $length, $authtypecode,
$sortby, $skipmetadata
);
returns ref to array result and count of results returned
Note: The parameters and_or and excluding are not supported currently.
The subexpressions in $tags are hardcoded with OR unless you pass the
parameter authtypecode. In the latter case AND is used.
Returns a results arrayref and a count of results
=cut
sub SearchAuthorities {
# NOTE: The parameters $and_or, $excluding below are UNUSED here.
my ( $tags, $and_or, $excluding, $operator, $value, $offset, $length, $authtypecode, $sortby, $skipmetadata ) = @_;
# warn Dumper($tags, $and_or, $excluding, $operator, $value, $offset,$length,$authtypecode,$sortby);
my $dbh = C4::Context->dbh;
$sortby = "" unless $sortby;
my $query;
@ -134,7 +142,7 @@ sub SearchAuthorities {
my @authtypecode;
my @auths = split / /, $authtypecode;
foreach my $auth (@auths) {
$query .= " \@attr 1=authtype \@attr 5=100 " . $auth; ##No truncation on authtype
$query .= " \@attr 1=authtype \@attr 5=100 \@attr 6=3 " . $auth; #5=100 No truncation, 6=3 Complete field
push @authtypecode, $auth;
$n++;
}
@ -612,16 +620,7 @@ sub AddAuthority {
}
my $date = POSIX::strftime( "%y%m%d", localtime );
if ( !$record->field('008') ) {
# Get a valid default value for field 008
my $default_008 = C4::Context->preference('MARCAuthorityControlField008');
if ( !$default_008 or length($default_008) < 34 ) {
$default_008 = '|| aca||aabn | a|a d';
} else {
$default_008 = substr( $default_008, 0, 34 );
}
$record->insert_fields_ordered( MARC::Field->new( '008', $date . $default_008 ) );
$record->insert_fields_ordered( MARC::Field->new( '008', $date . Koha::Authority->default_marc21_008 ) );
}
if ( !$record->field('040') ) {
$record->insert_fields_ordered(
@ -685,7 +684,6 @@ sub AddAuthority {
)->store();
$authority->discard_changes();
$authid = $authority->authid;
logaction( "AUTHORITIES", "ADD", $authid, "authority" ) if C4::Context->preference("AuthoritiesLog");
} else {
$action = 'modify';
$authority = Koha::Authorities->find($authid);
@ -715,6 +713,15 @@ sub AddAuthority {
}
_after_authority_action_hooks( { action => $action, authority_id => $authid } );
if ( $action eq 'create' && C4::Context->preference("AuthoritiesLog") ) {
$authority->discard_changes;
logaction(
"AUTHORITIES", "ADD", $authid, "authority", undef,
_authority_log_data( $authority, $record )
);
}
return ($authid);
}
@ -745,6 +752,13 @@ sub DelAuthority {
merge( { mergefrom => $authid } ) if !$skip_merge;
my $authority = Koha::Authorities->find($authid);
my $deleted_marc;
$deleted_marc = eval { $authority->record } if C4::Context->preference("AuthoritiesLog");
my $deleted_data =
C4::Context->preference("AuthoritiesLog")
? _authority_log_data( $authority, $deleted_marc )
: undef;
$schema->txn_do(
sub {
$authority->move_to_deleted; #FIXME We should define 'move' ..
@ -752,7 +766,8 @@ sub DelAuthority {
}
);
logaction( "AUTHORITIES", "DELETE", $authid, "authority" ) if C4::Context->preference("AuthoritiesLog");
logaction( "AUTHORITIES", "DELETE", $authid, "authority", undef, $deleted_data )
if C4::Context->preference("AuthoritiesLog");
unless ($skip_record_index) {
my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::AUTHORITIES_INDEX } );
$indexer->index_records( $authid, "recordDelete", "authorityserver", undef );
@ -784,13 +799,33 @@ sub ModAuthority {
my $skip_record_index = $params->{skip_record_index} || 0;
my $oldrecord = GetAuthority($authid);
my $original_data;
if ( C4::Context->preference("AuthoritiesLog") ) {
my $old_authority = Koha::Authorities->find($authid);
$original_data = _authority_log_data( $old_authority, $oldrecord );
}
#Now rewrite the $record to table with an add
$authid = AddAuthority( $record, $authid, $authtypecode, { skip_record_index => $skip_record_index } );
merge( { mergefrom => $authid, MARCfrom => $oldrecord, mergeto => $authid, MARCto => $record } )
if !$params->{skip_merge};
logaction( "AUTHORITIES", "MODIFY", $authid, "authority BEFORE=>" . $oldrecord->as_formatted )
if C4::Context->preference("AuthoritiesLog");
if ( C4::Context->preference("AuthoritiesLog") ) {
my $new_authority = Koha::Authorities->find($authid);
# Re-read the stored MARC so both sides of the diff come from the
# same XML parse path. The in-memory $record carries non-utf8 SVs
# for default indicators while GetAuthority/->record go through
# MARC::Record->new_from_xml which utf8-flags them; Struct::Diff
# treats those as different and emits phantom indicator entries.
my $new_record = eval { $new_authority->record } // $record;
logaction(
"AUTHORITIES", "MODIFY", $authid,
_authority_log_data( $new_authority, $new_record ),
undef, $original_data
);
}
return $authid;
}
@ -1076,11 +1111,12 @@ sub BuildSummary {
};
}
}
foreach my $field ( $record->field('4..') ) { #See From
my @fields = _marc21_sort_hierarchy_alpha( $record->field('4..') );
foreach my $field (@fields) { #See From
my $type = 'seefrom';
$type = ( $marc21controlrefs{ substr $field->subfield('w'), 0, 1 } || '' ) if ( $field->subfield('w') );
if ( $type eq 'notapplicable' ) {
$type = substr $field->subfield('w'), 2, 1;
$type = substr $field->subfield('w'), 2, 1 if length( $field->subfield('w') ) > 2;
$type = 'earlier' if $type && $type ne 'n';
}
if ( $type eq 'subfi' ) {
@ -1099,11 +1135,12 @@ sub BuildSummary {
};
}
}
foreach my $field ( $record->field('5..') ) { #See Also
@fields = _marc21_sort_hierarchy_alpha( $record->field('5..') );
foreach my $field (@fields) { #See Also
my $type = 'seealso';
$type = ( $marc21controlrefs{ substr $field->subfield('w'), 0, 1 } || '' ) if ( $field->subfield('w') );
if ( $type eq 'notapplicable' ) {
$type = substr $field->subfield('w'), 2, 1;
$type = substr $field->subfield('w'), 2, 1 if length( $field->subfield('w') ) > 2;
$type = 'earlier' if $type && $type ne 'n';
}
if ( $type eq 'subfi' ) {
@ -1231,6 +1268,32 @@ sub BuildSummary {
return \%summary;
}
=head2 _marc21_sort_hierarchy_alpha
my @sorted = _marc21_sort_hierarchy_alpha(@fields);
sorts 4xx/5xx fields by hierarchy using subfield $w (g=broader, h=narrower,
anything else between), then alphabetically by subfield $a.
=cut
sub _marc21_sort_hierarchy_alpha {
my @fields = @_;
return (
sort {
# Sort broader (g) - no hierarchy (tric:gh) - narrower (h)
# MARC21 $w has up to 4 positions; only position 0 encodes hierarchy.
my $a_hier = substr( $a->subfield('w') // q{}, 0, 1 );
$a_hier = 'gh' if $a_hier !~ /^[gh]$/;
my $b_hier = substr( $b->subfield('w') // q{}, 0, 1 );
$b_hier = 'gh' if $b_hier !~ /^[gh]$/;
# When hierarchy does not resolve, sort on $a
$a_hier cmp $b_hier || ( ( $a->subfield('a') // q{} ) cmp( $b->subfield('a') // q{} ) );
} @fields
);
}
=head2 GetAuthorizedHeading
$heading = &GetAuthorizedHeading({ record => $record, authid => $authid })
@ -1490,11 +1553,34 @@ sub GenerateHierarchy {
return \@loophierarchies;
}
=head2 _get_authid_subfield
Return the subfield that includes the authority id.
=cut
sub _get_authid_subfield {
my ($field) = @_;
return $field->subfield('9') || $field->subfield('3');
}
=head2 _authority_log_data
Returns a hashref combining the authority columns that are independent of
the MARC record with a C<_marc> key containing the record in MARC-in-JSON
format, suitable for passing to C<logaction> as either the C<$infos> or
C<$original> argument.
=cut
sub _authority_log_data {
my ( $authority, $marc_record ) = @_;
return {
%{ $authority->_unblessed_for_log },
_marc => ( $marc_record ? $marc_record->to_mij_structure : {} ),
};
}
=head2 GetHeaderAuthority
$ref= &GetHeaderAuthority( $authid)
@ -1685,6 +1771,7 @@ sub merge {
C4::Context->preference('IncludeSeeFromInSearches')
|| C4::Context->preference('IncludeSeeAlsoFromInSearches')
|| 0;
my @biblios_to_index;
while ( my $biblio = $biblios->next ) {
my $marcrecord = $biblio->metadata->record;
my $update = 0;
@ -1784,16 +1871,29 @@ sub merge {
&& $reindex_if_needed
&& $syspref_include_see_from )
{
my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
$indexer->index_records( $biblio->biblionumber, "specialUpdate", "biblioserver" );
push( @biblios_to_index, $biblio->biblionumber );
}
next if !$update;
ModBiblio( $marcrecord, $biblio->biblionumber, $biblio->frameworkcode, { disable_autolink => 1 } );
ModBiblio(
$marcrecord, $biblio->biblionumber, $biblio->frameworkcode,
{ disable_autolink => 1, skip_record_index => 1 }
);
push( @biblios_to_index, $biblio->biblionumber );
$counteditedbiblio++;
}
if (@biblios_to_index) {
my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
$indexer->index_records( \@biblios_to_index, "specialUpdate", "biblioserver" );
}
return $counteditedbiblio;
}
=head2 _merge_newtag
Returns subfields for merging when changing authtypecode.
=cut
sub _merge_newtag {
# Routine is only called for an (exceptional) authtypecode change

View File

@ -92,6 +92,33 @@ sub get_barcode {
return $nextnum;
}
package C4::Barcodes::ValueBuilder::EAN13;
use Modern::Perl;
use C4::Context;
use Algorithm::CheckDigits qw( CheckDigits );
sub get_barcode {
my ($args) = @_;
# Filter to 13-digit barcodes only so non-EAN-13 items (e.g. 14-digit sample
# barcodes) do not dominate max() and prevent the sequence from advancing.
my $query = "SELECT max(cast(barcode as unsigned)) FROM items WHERE length(barcode) = 13";
my $sth = C4::Context->dbh->prepare($query);
$sth->execute();
my ($nextnum) = $sth->fetchrow_array;
my $ean = CheckDigits('ean');
if ( $nextnum && $ean->is_valid($nextnum) ) {
my $next = $ean->basenumber($nextnum) + 1;
$nextnum = $ean->complete($next);
$nextnum = '0' x ( 13 - length($nextnum) ) . $nextnum; # pad zeros
} else {
warn "ERROR: invalid EAN-13 " . ( $nextnum // '' ) . ", using increment";
$nextnum = ( $nextnum // 0 ) + 1;
}
return $nextnum;
}
1;
=head1 Barcodes::ValueBuilder

View File

@ -75,6 +75,7 @@ use Try::Tiny qw( catch try );
use Encode;
use List::MoreUtils qw( uniq );
use MARC::Record;
use MARC::Record::MiJ;
use MARC::File::USMARC;
use MARC::File::XML;
use POSIX qw( strftime );
@ -95,8 +96,10 @@ use C4::Linker;
use C4::OAI::Sets;
use Koha::Logger;
use Koha::Cache::Memory::Lite;
use Koha::Caches;
use Koha::ClassSources;
use Koha::Authority;
use Koha::Authority::Types;
use Koha::Acquisition::Currencies;
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
@ -299,6 +302,7 @@ sub AddBiblio {
{
skip_record_index => 1,
record_source_id => $options->{record_source_id},
( ref $options->{warnings} eq 'ARRAY' ? ( warnings => $options->{warnings} ) : () ),
}
);
@ -306,22 +310,36 @@ sub AddBiblio {
if ( C4::Context->preference("OAI-PMH:AutoUpdateSets") ) {
C4::OAI::Sets::UpdateOAISetsBiblio( $biblionumber, $record );
}
_after_biblio_action_hooks( { action => 'create', biblio_id => $biblionumber } );
logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
}
);
_after_biblio_action_hooks( { action => 'create', biblio_id => $biblionumber } );
if ( C4::Context->preference("CataloguingLog") ) {
my $new_biblio = Koha::Biblios->find($biblionumber);
my $marc_for_log = eval { $new_biblio->metadata->record_strip_nonxml };
logaction(
"CATALOGUING", "ADD", $biblionumber, "biblio", undef,
{
%{ $new_biblio->_unblessed_for_log },
_marc => ( $marc_for_log ? $marc_for_log->to_mij_structure : {} )
}
);
}
# We index now, after the transaction is committed
unless ($skip_record_index) {
my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
$indexer->index_records( $biblionumber, "specialUpdate", "biblioserver" );
}
} catch {
warn $_;
( $biblionumber, $biblioitemnumber ) = ( undef, undef );
my $error = $_;
if ( ref($error) eq 'Koha::Exceptions::Metadata::Invalid' ) {
( $biblionumber, $biblioitemnumber ) = ( undef, undef );
$error->rethrow;
} else {
warn $error;
( $biblionumber, $biblioitemnumber ) = ( undef, undef );
}
};
return ( $biblionumber, $biblioitemnumber );
}
@ -389,7 +407,12 @@ sub ModBiblio {
( exists $options->{record_source_id} )
? ( record_source_id => $options->{record_source_id} )
: ()
)
),
(
( ref $options->{warnings} eq 'ARRAY' )
? ( warnings => $options->{warnings} )
: ()
),
};
if ( !$record ) {
@ -397,18 +420,11 @@ sub ModBiblio {
return 0;
}
my ( $original, $original_marc );
if ( C4::Context->preference("CataloguingLog") ) {
my $biblio = Koha::Biblios->find($biblionumber);
my $record;
my $decoding_error = "";
eval { $record = $biblio->metadata->record };
if ($@) {
my $exception = $@;
$exception->rethrow unless ( $exception->isa('Koha::Exceptions::Metadata::Invalid') );
$decoding_error = "There was an error with this bibliographic record: " . $exception;
$record = $biblio->metadata->record_strip_nonxml;
}
logaction( "CATALOGUING", "MODIFY", $biblionumber, "biblio $decoding_error BEFORE=>" . $record->as_formatted );
my $pre_biblio = Koha::Biblios->find($biblionumber);
$original = $pre_biblio->_unblessed_for_log;
$original_marc = eval { $pre_biblio->metadata->record_strip_nonxml };
}
if ( !$options->{disable_autolink} && C4::Context->preference('AutoLinkBiblios') ) {
@ -471,6 +487,20 @@ sub ModBiblio {
_after_biblio_action_hooks( { action => 'modify', biblio_id => $biblionumber } );
if ( C4::Context->preference("CataloguingLog") ) {
my $updated_biblio = Koha::Biblios->find($biblionumber);
my $updated_marc_log = eval { $updated_biblio->metadata->record_strip_nonxml };
logaction(
"CATALOGUING", "MODIFY", $biblionumber,
{
%{ $updated_biblio->_unblessed_for_log },
_marc => ( $updated_marc_log ? $updated_marc_log->to_mij_structure : {} )
},
undef,
{ %{$original}, _marc => ( $original_marc ? $original_marc->to_mij_structure : {} ) }
);
}
# update OAI-PMH sets
if ( C4::Context->preference("OAI-PMH:AutoUpdateSets") ) {
C4::OAI::Sets::UpdateOAISetsBiblio( $biblionumber, $record );
@ -538,6 +568,11 @@ sub DelBiblio {
my $dbh = C4::Context->dbh;
my $error; # for error handling
my $biblio_marc =
C4::Context->preference("CataloguingLog")
? eval { $biblio->metadata->record_strip_nonxml }
: undef;
# First make sure this biblio has no items attached
my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber=?");
$sth->execute($biblionumber);
@ -590,7 +625,10 @@ sub DelBiblio {
_after_biblio_action_hooks( { action => 'delete', biblio_id => $biblionumber } );
logaction( "CATALOGUING", "DELETE", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
logaction(
"CATALOGUING", "DELETE", $biblionumber, "biblio", undef,
{ %{ $biblio->_unblessed_for_log }, _marc => ( $biblio_marc ? $biblio_marc->to_mij_structure : {} ) }
) if C4::Context->preference("CataloguingLog");
Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue( { biblio_ids => [$biblionumber] } )
unless $params->{skip_holds_queue}
@ -774,10 +812,47 @@ sub LinkBibHeadingsToAuthorities {
if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
my $userenv = C4::Context->userenv;
my $library;
if ( $userenv && $userenv->{'branch'} ) {
$library = Koha::Libraries->find( $userenv->{'branch'} );
# userenv's library could not exist because of a trick in misc/commit_file.pl
# (see FIXME and set_userenv statement there)
my $library =
$userenv && $userenv->{branch} ? Koha::Libraries->find( $userenv->{branch} ) : undef;
my $marcorgcode =
$library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode');
# Bug 31925: code the new authority with the source heading's actual
# thesaurus instead of relying on AddAuthority()'s LCSH-only default.
# AddAuthority() only defaults 008/040 when they aren't already present
# on the record, so pre-populating them here is sufficient - no change
# to AddAuthority() itself is needed. Skip this when the heading's
# thesaurus couldn't actually be identified (bug 31925 comment 30) - in
# that case the site's own MARCAuthorityControlField008 default should
# apply, the same as it always has, rather than being overridden. Also
# skip it entirely when LinkerConsiderThesaurus is off (bug 31925 comment
# 45): with thesaurus-aware matching disabled, headings from any thesaurus
# can link to the same auto-created authority by text alone, so coding
# that authority for whichever heading happened to create it would let its
# thesaurus silently propagate onto unrelated bibs the next time the
# authority is saved - only sites that opted into thesaurus-aware linking
# (and so already search by thesaurus) should get thesaurus-aware creation.
if ( C4::Context->preference('LinkerConsiderThesaurus')
&& Koha::Authority->has_known_thesaurus( $heading->{thesaurus} ) )
{
my $date = POSIX::strftime( '%y%m%d', localtime );
$marcrecordauth->insert_fields_ordered(
MARC::Field->new(
'008', $date . Koha::Authority->default_marc21_008( $heading->{thesaurus} )
)
);
my @f040_subfields = ( 'a' => $marcorgcode, 'c' => $marcorgcode );
push @f040_subfields, ( 'f' => $heading->{thesaurus} )
unless exists
$Koha::Authority::MARC21_THESAURUS_TO_CONTROL_FIELD_008_11->{ $heading->{thesaurus} };
$marcrecordauth->insert_fields_ordered(
MARC::Field->new( '040', '', '', @f040_subfields ) );
}
$marcrecordauth->insert_fields_ordered(
MARC::Field->new(
'667', '', '',
@ -788,10 +863,8 @@ sub LinkBibHeadingsToAuthorities {
$cite =~ s/^[\s\,]*//;
$cite =~ s/[\s\,]*$//;
$cite =
C4::Context->preference('GenerateAuthorityField670') . ": ("
. (
$library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode') )
. ")"
C4::Context->preference('GenerateAuthorityField670') . ": ("
. "$marcorgcode)"
. $bib->subfield( '999', 'c' ) . ": "
. $cite;
$marcrecordauth->insert_fields_ordered( MARC::Field->new( '670', '', '', 'a' => $cite ) );
@ -1522,15 +1595,15 @@ sub GetAuthorisedValueDesc {
#---- itemtypes
if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "itemtypes" ) {
my $lang = C4::Languages::getlanguage;
$lang //= 'en';
$cache_key = 'itemtype:description:' . $lang;
my $itypes = $cache->get_from_cache( $cache_key, { unsafe => 1 } );
if ( !$itypes ) {
$itypes = { map { $_->itemtype => $_->translated_description } Koha::ItemTypes->search()->as_list };
$cache->set_in_cache( $cache_key, $itypes );
my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
my $cache_key = 'GetAuthorisedValueDesc:itemtypes';
my $itemtypes = $memory_cache->get_from_cache($cache_key);
unless ($itemtypes) {
$itemtypes = { map { $_->itemtype => $_ } Koha::ItemTypes->as_list };
$memory_cache->set_in_cache( $cache_key, $itemtypes );
}
return $itypes->{$value};
my $itemtype = $itemtypes->{$value};
return $itemtype ? $itemtype->translated_description : undef;
}
if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "cn_source" ) {
@ -1562,7 +1635,7 @@ sub GetAuthorisedValueDesc {
Koha::AuthorisedValues->search(
{ category => $category },
{ columns => [ 'authorised_value', 'lib_opac', 'lib' ] }
)->as_list
)->as_list
};
$cache->set_in_cache( $cache_key, $av_descriptions );
}
@ -1976,6 +2049,18 @@ sub TransformKohaToMarc {
return $record;
}
=head2 _check_split
my $bool = _check_split( $params, $fld, $value );
Checks whether C<$value> should be split into repeatable subfields. Returns
true if the field is repeatable (consulting the passed framework when
provided, otherwise falling back to the default framework structure), false
otherwise. Returns undef if C<$value> contains no pipe character or splitting
is disabled via C<$params->{no_split}>.
=cut
sub _check_split {
# Checks if $value must be split; may consult passed framework
@ -2315,7 +2400,7 @@ sub TransformHtmlToMarc {
# between 001 and 009 (included)
} elsif ( $fval ne '' ) {
$fval = StripNonXmlChars($fval); #Strip out any non-XML characters like control characters
$fval = StripNonXmlChars($fval);
$newfield = MARC::Field->new( $tag, $fval, );
}
@ -2340,7 +2425,7 @@ sub TransformHtmlToMarc {
if ( $fval ne '' && $newfield ) {
$newfield->add_subfields( $fkey => $fval );
} elsif ( $fval ne '' ) {
$fval = StripNonXmlChars($fval); #Strip out any non-XML characters like control characters
$fval = StripNonXmlChars($fval);
$newfield = MARC::Field->new( $tag, $ind1, $ind2, $fkey => $fval );
}
$j += 2;
@ -2917,7 +3002,6 @@ sub ModBiblioMarc {
# Clone record as it gets modified
$record = $record->clone();
my $dbh = C4::Context->dbh;
my @fields = $record->fields();
my $encoding = C4::Context->preference("marcflavour");
@ -2994,6 +3078,12 @@ sub ModBiblioMarc {
}
)->store;
if ( ref $options->{warnings} eq 'ARRAY' ) {
for my $msg ( grep { $_->type eq 'warning' } @{ $m_rs->object_messages } ) {
push @{ $options->{warnings} }, { type => $msg->message, message => $msg->payload };
}
}
unless ($skip_record_index) {
my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
$indexer->index_records( $biblionumber, "specialUpdate", "biblioserver" );

View File

@ -155,7 +155,9 @@ sub Z3950Search {
}
my $xslh = Koha::XSLT::Base->new;
my $nremaining = $s;
my $nremaining = $s;
my $hits_per_page = 20; #TODO make configurable
my $total_hits = 0;
while ( $nremaining-- ) {
my $k;
my $event;
@ -169,18 +171,22 @@ sub Z3950Search {
my ($error) = $oConnection[$k]->error_x(); #ignores errmsg, addinfo, diagset
if ($error) {
if ( $error =~ m/^(10000|10007)$/ ) {
push( @errconn, { server => $servers[$k]->{host}, error => $error } );
push( @errconn, { server => $servers[$k]->{host}, errcode => $error } );
}
} else {
my $numresults = $oResult[$k]->size();
$total_hits += $numresults;
my $i;
my $res;
if ( $numresults > 0 and $numresults >= ( ( $page - 1 ) * 20 ) ) {
$show_next = 1 if $numresults >= ( $page * 20 );
$total_pages = int( $numresults / 20 ) + 1 if $total_pages < ( $numresults / 20 );
if ( $numresults > 0 and $numresults >= ( ( $page - 1 ) * $hits_per_page ) ) {
$show_next = 1 if $numresults >= ( $page * $hits_per_page );
$total_pages = int( $numresults / $hits_per_page ) + 1
if $total_pages < ( $numresults / $hits_per_page );
for (
$i = ( $page - 1 ) * 20 ;
$i < ( ( $numresults < ( $page * 20 ) ) ? $numresults : ( $page * 20 ) ) ;
$i = ( $page - 1 ) * $hits_per_page ;
$i < (
( $numresults < ( $page * $hits_per_page ) ) ? $numresults : ( $page * $hits_per_page )
) ;
$i++
)
{
@ -191,14 +197,17 @@ sub Z3950Search {
$xslh
); #ignores error in sequence numbering
push @breeding_loop, $res if $res;
push @errconn, { server => $servers[$k]->{servername}, error => $error, seq => $i + 1 }
push @errconn, { server => $servers[$k]->{servername}, errcode => $error, seq => $i + 1 }
if $error;
} else {
my @diags = $oConnection[$k]->error_x(); # errcode, errmsg, addinfo, diagset
next unless $diags[0]; # Just silently ignore empty results
push @errconn,
{
'server' => $servers[$k]->{servername},
error => ( ( $oConnection[$k]->error_x() )[0] ),
error_msg => ( ( $oConnection[$k]->error_x() )[1] ), seq => $i + 1
server => $servers[$k]->{servername},
errcode => $diags[0],
errmsg => $diags[1],
seq => $i + 1,
};
}
}
@ -212,6 +221,7 @@ sub Z3950Search {
total_pages => $total_pages,
show_nextbutton => $show_next ? 1 : 0,
show_prevbutton => $page != 1,
total_hits => $total_hits,
);
} # while nremaining

View File

@ -62,7 +62,6 @@ BEGIN {
GetBudgetChildren
SetOwnerToFundHierarchy
CheckBudgetParent
CheckBudgetParentPerm
HideCols
GetCols
@ -136,30 +135,11 @@ sub GetCols {
}
=head2 CheckBudgetParentPerm
Missing POD for CheckBudgetParentPerm.
=cut
sub CheckBudgetParentPerm {
my ( $budget, $borrower_id ) = @_;
my $depth = $budget->{depth};
my $parent_id = $budget->{budget_parent_id};
while ($depth) {
my $parent = GetBudget($parent_id);
$parent_id = $parent->{budget_parent_id};
if ( $parent->{budget_owner_id} == $borrower_id ) {
return 1;
}
$depth--;
}
return 0;
}
=head2 AddBudgetPeriod
Missing POD for AddBudgetPeriod.
Create a BudgetPeriod period with given parameters
my $rs = AddBudgetPeriod({ budget_period_startdate => $start_date, budget_period_enddate => $budget_period_enddate });
=cut

View File

@ -21,6 +21,7 @@ use vars qw(@EXPORT);
use Carp qw( croak );
use Date::Calc qw( Today );
use Koha::Library::Calendar;
use C4::Context;
use Koha::Caches;
@ -64,30 +65,32 @@ sub new {
return $self;
}
=head2 _init
Loads calendar data from the database for the given branch.
=cut
sub _init {
my $self = shift @_;
my $branch = shift;
defined($branch) or die "No branchcode sent to _init"; # must test for defined here and above to allow ""
my $dbh = C4::Context->dbh();
my $repeatable = $dbh->prepare(
'SELECT *
FROM repeatable_holidays
WHERE ( branchcode = ? )
AND (ISNULL(weekday) = ?)'
);
$repeatable->execute( $branch, 0 );
my %week_days_holidays;
my $dbh = C4::Context->dbh();
while ( my $row = $repeatable->fetchrow_hashref ) {
my $weekly_sth = $dbh->prepare('SELECT * FROM library_weekly_closures WHERE library_id = ?');
$weekly_sth->execute($branch);
my %week_days_holidays;
while ( my $row = $weekly_sth->fetchrow_hashref ) {
my $key = $row->{weekday};
$week_days_holidays{$key}{title} = $row->{title};
$week_days_holidays{$key}{description} = $row->{description};
}
$self->{'week_days_holidays'} = \%week_days_holidays;
$repeatable->execute( $branch, 1 );
my $repeating_sth = $dbh->prepare('SELECT * FROM library_repeating_closures WHERE library_id = ?');
$repeating_sth->execute($branch);
my %day_month_holidays;
while ( my $row = $repeatable->fetchrow_hashref ) {
while ( my $row = $repeating_sth->fetchrow_hashref ) {
my $key = $row->{month} . "/" . $row->{day};
$day_month_holidays{$key}{title} = $row->{title};
$day_month_holidays{$key}{description} = $row->{description};
@ -96,29 +99,26 @@ sub _init {
}
$self->{'day_month_holidays'} = \%day_month_holidays;
my $special = $dbh->prepare(
'SELECT day, month, year, title, description
FROM special_holidays
WHERE ( branchcode = ? )
AND (isexception = ?)'
);
$special->execute( $branch, 1 );
my $exceptions_sth =
$dbh->prepare('SELECT date, title, description FROM library_closure_exceptions WHERE library_id = ?');
$exceptions_sth->execute($branch);
my %exception_holidays;
while ( my ( $day, $month, $year, $title, $description ) = $special->fetchrow ) {
$exception_holidays{"$year/$month/$day"}{title} = $title;
$exception_holidays{"$year/$month/$day"}{description} = $description;
$exception_holidays{"$year/$month/$day"}{date} =
sprintf( ISO_DATE_FORMAT, $year, $month, $day );
while ( my $row = $exceptions_sth->fetchrow_hashref ) {
my ( $year, $month, $day ) = map { $_ + 0 } split( /-/, $row->{date} );
$exception_holidays{"$year/$month/$day"}{title} = $row->{title};
$exception_holidays{"$year/$month/$day"}{description} = $row->{description};
$exception_holidays{"$year/$month/$day"}{date} = $row->{date};
}
$self->{'exception_holidays'} = \%exception_holidays;
$special->execute( $branch, 0 );
my $single_sth = $dbh->prepare('SELECT date, title, description FROM library_single_closures WHERE library_id = ?');
$single_sth->execute($branch);
my %single_holidays;
while ( my ( $day, $month, $year, $title, $description ) = $special->fetchrow ) {
$single_holidays{"$year/$month/$day"}{title} = $title;
$single_holidays{"$year/$month/$day"}{description} = $description;
$single_holidays{"$year/$month/$day"}{date} =
sprintf( ISO_DATE_FORMAT, $year, $month, $day );
while ( my $row = $single_sth->fetchrow_hashref ) {
my ( $year, $month, $day ) = map { $_ + 0 } split( /-/, $row->{date} );
$single_holidays{"$year/$month/$day"}{title} = $row->{title};
$single_holidays{"$year/$month/$day"}{description} = $row->{description};
$single_holidays{"$year/$month/$day"}{date} = $row->{date};
}
$self->{'single_holidays'} = \%single_holidays;
return $self;
@ -202,17 +202,13 @@ C<$description> Is the description to store for the holiday formed by $year/$mon
sub insert_week_day_holiday {
my $self = shift @_;
my %options = @_;
my $weekday = $options{weekday};
croak "Invalid weekday $weekday" unless $weekday =~ m/^[0-6]$/;
my $dbh = C4::Context->dbh();
my $insertHoliday = $dbh->prepare(
"insert into repeatable_holidays (branchcode,weekday,day,month,title,description) values ( ?,?,NULL,NULL,?,? )"
Koha::Library::Calendar->new( branchcode => $self->{branchcode} )->add_weekly_closure(
{
weekday => $options{weekday}, title => $options{title}, description => $options{description},
}
);
$insertHoliday->execute( $self->{branchcode}, $weekday, $options{title}, $options{description} );
$self->{'week_days_holidays'}->{$weekday}{title} = $options{title};
$self->{'week_days_holidays'}->{$weekday}{description} = $options{description};
$self->{'week_days_holidays'}->{ $options{weekday} }{title} = $options{title};
$self->{'week_days_holidays'}->{ $options{weekday} }{description} = $options{description};
return $self;
}
@ -238,14 +234,11 @@ C<$description> Is the description to store for the holiday formed by $year/$mon
sub insert_day_month_holiday {
my $self = shift @_;
my %options = @_;
my $dbh = C4::Context->dbh();
my $insertHoliday = $dbh->prepare(
"insert into repeatable_holidays (branchcode,weekday,day,month,title,description) values (?, NULL, ?, ?, ?,? )"
);
$insertHoliday->execute(
$self->{branchcode}, $options{day}, $options{month}, $options{title},
$options{description}
Koha::Library::Calendar->new( branchcode => $self->{branchcode} )->add_repeating_closure(
{
day => $options{day}, month => $options{month}, title => $options{title},
description => $options{description},
}
);
$self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{title} = $options{title};
$self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{description} = $options{description};
@ -277,28 +270,23 @@ C<$description> Is the description to store for the holiday formed by $year/$mon
sub insert_single_holiday {
my $self = shift @_;
my %options = @_;
@options{qw(year month day)} = ( $options{date} =~ m/(\d+)-(\d+)-(\d+)/o )
if $options{date} && !$options{day};
my $date = $options{date};
unless ($date) {
$date = sprintf( ISO_DATE_FORMAT, $options{year}, $options{month}, $options{day} );
}
@options{qw(year month day)} = map { $_ + 0 } ( $date =~ m/(\d+)-(\d+)-(\d+)/ )
unless $options{day};
my $dbh = C4::Context->dbh();
my $isexception = 0;
my $insertHoliday = $dbh->prepare(
"insert into special_holidays (branchcode,day,month,year,isexception,title,description) values (?,?,?,?,?,?,?)"
);
$insertHoliday->execute(
$self->{branchcode}, $options{day}, $options{month}, $options{year}, $isexception,
$options{title}, $options{description}
Koha::Library::Calendar->new( branchcode => $self->{branchcode} )->add_single_closure(
{
date => $date, title => $options{title}, description => $options{description},
}
);
$self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
$self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
# changed the 'single_holidays' table, lets force/reset its cache
my $cache = Koha::Caches->get_instance();
my $key = $self->{branchcode} . "_holidays";
$cache->clear_from_cache($key);
return $self;
}
=head2 insert_exception_holiday
@ -326,28 +314,23 @@ C<$description> Is the description to store for the holiday formed by $year/$mon
sub insert_exception_holiday {
my $self = shift @_;
my %options = @_;
my $date = $options{date};
unless ($date) {
$date = sprintf( ISO_DATE_FORMAT, $options{year}, $options{month}, $options{day} );
}
@options{qw(year month day)} = map { $_ + 0 } ( $date =~ m/(\d+)-(\d+)-(\d+)/ )
unless $options{day};
@options{qw(year month day)} = ( $options{date} =~ m/(\d+)-(\d+)-(\d+)/o )
if $options{date} && !$options{day};
Koha::Library::Calendar->new( branchcode => $self->{branchcode} )->add_exception(
{
date => $date, title => $options{title}, description => $options{description},
}
);
my $dbh = C4::Context->dbh();
my $isexception = 1;
my $insertException = $dbh->prepare(
"insert into special_holidays (branchcode,day,month,year,isexception,title,description) values (?,?,?,?,?,?,?)"
);
$insertException->execute(
$self->{branchcode}, $options{day}, $options{month}, $options{year}, $isexception,
$options{title}, $options{description}
);
$self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
$self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} =
$options{description};
# changed the 'single_holidays' table, lets force/reset its cache
my $cache = Koha::Caches->get_instance();
my $key = $self->{branchcode} . "_holidays";
$cache->clear_from_cache($key);
return $self;
}
@ -368,13 +351,10 @@ C<$description> Is the description to update for the holiday.
sub ModWeekdayholiday {
my $self = shift @_;
my %options = @_;
my $dbh = C4::Context->dbh();
my $updateHoliday =
$dbh->prepare("UPDATE repeatable_holidays SET title = ?, description = ? WHERE branchcode = ? AND weekday = ?");
$updateHoliday->execute( $options{title}, $options{description}, $self->{branchcode}, $options{weekday} );
$self->{'week_days_holidays'}->{ $options{weekday} }{title} = $options{title};
$self->{'week_days_holidays'}->{ $options{weekday} }{description} = $options{description};
my $closure =
Koha::Library::Calendar::WeeklyClosures->search(
{ library_id => $self->{branchcode}, weekday => $options{weekday} } )->next;
$closure->title( $options{title} )->description( $options{description} )->store if $closure;
return $self;
}
@ -400,16 +380,9 @@ C<$description> The description to be update for the holiday.
sub ModDaymonthholiday {
my $self = shift @_;
my %options = @_;
my $dbh = C4::Context->dbh();
my $updateHoliday = $dbh->prepare(
"UPDATE repeatable_holidays SET title = ?, description = ? WHERE month = ? AND day = ? AND branchcode = ?");
$updateHoliday->execute(
$options{title}, $options{description}, $options{month}, $options{day},
$self->{branchcode}
);
$self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{title} = $options{title};
$self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{description} = $options{description};
my $closure = Koha::Library::Calendar::RepeatingClosures->search(
{ library_id => $self->{branchcode}, day => $options{day}, month => $options{month} } )->next;
$closure->title( $options{title} )->description( $options{description} )->store if $closure;
return $self;
}
@ -438,25 +411,12 @@ C<$description> Is the description to update for the holiday formed by $year/$mo
sub ModSingleholiday {
my $self = shift @_;
my %options = @_;
my $dbh = C4::Context->dbh();
my $isexception = 0;
my $updateHoliday = $dbh->prepare( "
UPDATE special_holidays SET title = ?, description = ?
WHERE day = ? AND month = ? AND year = ? AND branchcode = ? AND isexception = ?" );
$updateHoliday->execute(
$options{title}, $options{description}, $options{day}, $options{month}, $options{year},
$self->{branchcode}, $isexception
);
$self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
$self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
# changed the 'single_holidays' table, lets force/reset its cache
my $cache = Koha::Caches->get_instance();
my $key = $self->{branchcode} . "_holidays";
$cache->clear_from_cache($key);
my $date = $options{date} || sprintf( ISO_DATE_FORMAT, $options{year}, $options{month}, $options{day} );
my $closure =
Koha::Library::Calendar::SingleClosures->search( { library_id => $self->{branchcode}, date => $date } )->next;
if ($closure) {
$closure->title( $options{title} )->description( $options{description} )->store;
}
return $self;
}
@ -485,25 +445,12 @@ C<$description> Is the description to be modified for the holiday formed by $yea
sub ModExceptionholiday {
my $self = shift @_;
my %options = @_;
my $dbh = C4::Context->dbh();
my $isexception = 1;
my $updateHoliday = $dbh->prepare( "
UPDATE special_holidays SET title = ?, description = ?
WHERE day = ? AND month = ? AND year = ? AND branchcode = ? AND isexception = ?" );
$updateHoliday->execute(
$options{title}, $options{description}, $options{day}, $options{month}, $options{year},
$self->{branchcode}, $isexception
);
$self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
$self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} =
$options{description};
# changed the 'single_holidays' table, lets force/reset its cache
my $cache = Koha::Caches->get_instance();
my $key = $self->{branchcode} . "_holidays";
$cache->clear_from_cache($key);
my $date = $options{date} || sprintf( ISO_DATE_FORMAT, $options{year}, $options{month}, $options{day} );
my $exception =
Koha::Library::Calendar::Exceptions->search( { library_id => $self->{branchcode}, date => $date } )->next;
if ($exception) {
$exception->title( $options{title} )->description( $options{description} )->store;
}
return $self;
}
@ -529,67 +476,40 @@ C<$year> Is year to make the date to delete.
sub delete_holiday {
my $self = shift @_;
my %options = @_;
my $cal = Koha::Library::Calendar->new( branchcode => $self->{branchcode} );
# Verify what kind of holiday that day is. For example, if it is
# a repeatable holiday, this should check if there are some exception
# for that holiday rule. Otherwise, if it is a regular holiday, it´s
# ok just deleting it.
if ( $options{day} && $options{month} && $options{year} ) {
my $date = sprintf( ISO_DATE_FORMAT, $options{year}, $options{month}, $options{day} );
my $dbh = C4::Context->dbh();
my $isSingleHoliday = $dbh->prepare(
"SELECT id FROM special_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?) AND (year = ?)");
$isSingleHoliday->execute( $self->{branchcode}, $options{day}, $options{month}, $options{year} );
if ( $isSingleHoliday->rows ) {
my $id = $isSingleHoliday->fetchrow;
$isSingleHoliday->finish; # Close the last query
my $deleteHoliday = $dbh->prepare("DELETE FROM special_holidays WHERE id = ?");
$deleteHoliday->execute($id);
delete( $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"} );
} else {
$isSingleHoliday->finish; # Close the last query
my $isWeekdayHoliday = $dbh->prepare("SELECT id FROM repeatable_holidays WHERE branchcode = ? AND weekday = ?");
$isWeekdayHoliday->execute( $self->{branchcode}, $options{weekday} );
if ( $isWeekdayHoliday->rows ) {
my $id = $isWeekdayHoliday->fetchrow;
$isWeekdayHoliday->finish; # Close the last query
my $updateExceptions = $dbh->prepare(
"UPDATE special_holidays SET isexception = 0 WHERE (WEEKDAY(CONCAT(special_holidays.year,'-',special_holidays.month,'-',special_holidays.day)) = ?) AND (branchcode = ?)"
);
$updateExceptions->execute( $options{weekday}, $self->{branchcode} );
$updateExceptions->finish; # Close the last query
my $deleteHoliday = $dbh->prepare("DELETE FROM repeatable_holidays WHERE id = ?");
$deleteHoliday->execute($id);
delete( $self->{'week_days_holidays'}->{ $options{weekday} } );
} else {
$isWeekdayHoliday->finish; # Close the last query
my $isDayMonthHoliday = $dbh->prepare(
"SELECT id FROM repeatable_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?)");
$isDayMonthHoliday->execute( $self->{branchcode}, $options{day}, $options{month} );
if ( $isDayMonthHoliday->rows ) {
my $id = $isDayMonthHoliday->fetchrow;
$isDayMonthHoliday->finish;
my $updateExceptions = $dbh->prepare(
"UPDATE special_holidays SET isexception = 0 WHERE (special_holidays.branchcode = ?) AND (special_holidays.day = ?) and (special_holidays.month = ?)"
);
$updateExceptions->execute( $self->{branchcode}, $options{day}, $options{month} );
$updateExceptions->finish; # Close the last query
my $deleteHoliday = $dbh->prepare("DELETE FROM repeatable_holidays WHERE (id = ?)");
$deleteHoliday->execute($id);
delete( $self->{'day_month_holidays'}->{"$options{month}/$options{day}"} );
}
# Try single closure first
if ( Koha::Library::Calendar::SingleClosures->search( { library_id => $self->{branchcode}, date => $date } )
->count )
{
$cal->delete_single_closure( { date => $date } );
}
# Also remove any exception for this date
Koha::Library::Calendar::Exceptions->search( { library_id => $self->{branchcode}, date => $date } )->delete;
}
# changed the 'single_holidays' table, lets force/reset its cache
my $cache = Koha::Caches->get_instance();
my $key = $self->{branchcode} . "_holidays";
$cache->clear_from_cache($key);
if (
defined $options{weekday}
&& Koha::Library::Calendar::WeeklyClosures->search(
{ library_id => $self->{branchcode}, weekday => $options{weekday} }
)->count
)
{
$cal->delete_weekly_closure( { weekday => $options{weekday} } );
} elsif (
$options{day}
&& $options{month}
&& Koha::Library::Calendar::RepeatingClosures->search(
{ library_id => $self->{branchcode}, day => $options{day}, month => $options{month} }
)->count
)
{
$cal->delete_repeating_closure( { day => $options{day}, month => $options{month} } );
}
return $self;
}
@ -613,17 +533,8 @@ C<$year> Is year to make the date to delete.
sub delete_holiday_range {
my $self = shift;
my %options = @_;
my $dbh = C4::Context->dbh();
my $sth = $dbh->prepare(
"DELETE FROM special_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?) AND (year = ?)");
$sth->execute( $self->{branchcode}, $options{day}, $options{month}, $options{year} );
# changed the 'single_holidays' table, lets force/reset its cache
my $cache = Koha::Caches->get_instance();
my $key = $self->{branchcode} . "_holidays";
$cache->clear_from_cache($key);
my $date = $options{date} || sprintf( ISO_DATE_FORMAT, $options{year}, $options{month}, $options{day} );
Koha::Library::Calendar->new( branchcode => $self->{branchcode} )->delete_single_closure( { date => $date } );
}
=head2 delete_holiday_range_repeatable
@ -642,15 +553,8 @@ C<$month> Is month to make the date to delete.
sub delete_holiday_range_repeatable {
my $self = shift;
my %options = @_;
my $dbh = C4::Context->dbh();
my $sth = $dbh->prepare("DELETE FROM repeatable_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?)");
$sth->execute( $self->{branchcode}, $options{day}, $options{month} );
# changed the 'single_holidays' table, lets force/reset its cache
my $cache = Koha::Caches->get_instance();
my $key = $self->{branchcode} . "_holidays";
$cache->clear_from_cache($key);
Koha::Library::Calendar->new( branchcode => $self->{branchcode} )
->delete_repeating_closure( { day => $options{day}, month => $options{month} } );
}
=head2 delete_exception_holiday_range
@ -673,17 +577,8 @@ C<$year> Is year to make the date to delete.
sub delete_exception_holiday_range {
my $self = shift;
my %options = @_;
my $dbh = C4::Context->dbh();
my $sth = $dbh->prepare(
"DELETE FROM special_holidays WHERE (branchcode = ?) AND (isexception = 1) AND (day = ?) AND (month = ?) AND (year = ?)"
);
$sth->execute( $self->{branchcode}, $options{day}, $options{month}, $options{year} );
# changed the 'single_holidays' table, lets force/reset its cache
my $cache = Koha::Caches->get_instance();
my $key = $self->{branchcode} . "_holidays";
$cache->clear_from_cache($key);
my $date = $options{date} || sprintf( ISO_DATE_FORMAT, $options{year}, $options{month}, $options{day} );
Koha::Library::Calendar->new( branchcode => $self->{branchcode} )->delete_exception( { date => $date } );
}
=head2 isHoliday
@ -734,47 +629,7 @@ sub isHoliday {
sub copy_to_branch {
my ( $self, $target_branch ) = @_;
croak "No target_branch" unless $target_branch;
my $target_calendar = C4::Calendar->new( branchcode => $target_branch );
my ( $y, $m, $d ) = Today();
my $today = sprintf ISO_DATE_FORMAT, $y, $m, $d;
my $wdh = $self->get_week_days_holidays;
my $target_wdh = $target_calendar->get_week_days_holidays;
foreach my $key ( keys %$wdh ) {
unless ( grep { $_ eq $key } keys %$target_wdh ) {
$target_calendar->insert_week_day_holiday( weekday => $key, %{ $wdh->{$key} } );
}
}
my $dmh = $self->get_day_month_holidays;
my $target_dmh = $target_calendar->get_day_month_holidays;
foreach my $values ( values %$dmh ) {
unless ( grep { $_->{day} eq $values->{day} && $_->{month} eq $values->{month} } values %$target_dmh ) {
$target_calendar->insert_day_month_holiday( %{$values} );
}
}
my $exception_holidays = $self->get_exception_holidays;
my $target_exceptions = $target_calendar->get_exception_holidays;
foreach my $values ( grep { $_->{date} gt $today } values %{$exception_holidays} ) {
unless ( grep { $_->{date} eq $values->{date} } values %$target_exceptions ) {
$target_calendar->insert_exception_holiday( %{$values} );
}
}
my $single_holidays = $self->get_single_holidays;
my $target_singles = $target_calendar->get_single_holidays;
foreach my $values ( grep { $_->{date} gt $today } values %{$single_holidays} ) {
unless ( grep { $_->{date} eq $values->{date} } values %$target_singles ) {
$target_calendar->insert_single_holiday( %{$values} );
}
}
return 1;
return Koha::Library::Calendar->new( branchcode => $self->{branchcode} )->copy_to($target_branch);
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,9 @@ package C4::ClassSplitRoutine::RegEx;
use Modern::Perl;
use Koha::Logger;
use Koha::Regex::Replacement;
=head1 NAME
C4::ClassSplitRoutine::RegEx - regex call number sorting key routine
@ -41,13 +44,62 @@ sub split_callnumber {
my ( $cn_item, $regexs ) = @_;
for my $regex (@$regexs) {
eval "\$cn_item =~ $regex"; ## no critic (StringyEval)
_apply_substitution( \$cn_item, $regex );
}
my @lines = split "\n", $cn_item;
return @lines;
}
=head2 _apply_substitution
_apply_substitution( \$string, $regex );
Applies a single admin-configured s/// substitution to $string in place,
without ever evaluating the input as code. The rule is parsed as
C<s/PATTERN/REPLACEMENT/FLAGS> (any non-space delimiter is accepted); the pattern is compiled with
qr// (so embedded-code assertions are treated as a runtime pattern and blocked
by Perl), and the replacement is expanded as data via
Koha::Regex::Replacement::expand_template. The
/e flag is rejected and anything that is not a plain substitution is ignored.
This replaces the previous C<eval "\$cn_item =~ $regex"> stringy eval, which
executed arbitrary Perl.
=cut
sub _apply_substitution {
my ( $ref, $regex ) = @_;
return unless defined $regex;
# Parse s<DELIM>PATTERN<DELIM>REPLACEMENT<DELIM>FLAGS, allowing any
# non-space delimiter and backslash-escaped delimiters inside the pattern
# and replacement. Anything that is not a well-formed plain substitution is
# ignored rather than executed.
return unless $regex =~ m{^s(\S)((?:\\.|(?!\1).)*)\1((?:\\.|(?!\1).)*)\1([a-z]*)\z}s;
my ( $pattern, $replacement, $flags ) = ( $2, $3, $4 );
return if $flags =~ /e/; # never evaluate the replacement as code
my $global = $flags =~ /g/ ? 1 : 0;
my $ci = $flags =~ /i/ ? '(?i)' : '';
my $compiled = eval { qr/$ci$pattern/ };
unless ( defined $compiled ) {
Koha::Logger->get->warn("C4::ClassSplitRoutine::RegEx: failed to compile pattern [$pattern]: $@");
return;
}
my $expand = sub { Koha::Regex::Replacement::expand_template( $replacement, [ @{^CAPTURE} ], {%+} ) };
if ($global) {
${$ref} =~ s/$compiled/$expand->()/ge;
} else {
${$ref} =~ s/$compiled/$expand->()/e;
}
return;
}
1;
=head1 AUTHOR

View File

@ -45,11 +45,16 @@ use YAML::XS;
use ZOOM;
use List::MoreUtils qw(any);
use Koha;
use Koha::Caches;
use Koha::Config::SysPref;
use Koha::Config::SysPrefs;
use Koha::Config;
use Koha;
use Koha::Database;
# NOTE: The below list should match the corresponding options in the .pref file
# for CSVDelimiter
use constant ALLOWED_CSV_DELIMITERS => ( ';', 'tabulation', ',', '/', '\\', '#' );
=head1 NAME
@ -397,12 +402,18 @@ sub set_preference {
$variable = lc $variable;
my $syspref = Koha::Config::SysPrefs->find($variable);
$type =
$type ? $type
: $syspref ? $syspref->type
: undef;
$value = 0 if ( $type && $type eq 'YesNo' && $value eq '' );
# Deal with Boolean syspref, set to 0 if passed an empty string
if ( $value eq '' ) {
my $yaml_prefs = Koha::Config::SysPrefs->get_all_from_yml;
my $yaml_pref = $yaml_prefs->{$variable_case};
if ( $yaml_pref && $yaml_pref->{type} eq 'select' && ref( $yaml_pref->{choices} ) ) {
my @choices = sort keys %{ $yaml_pref->{choices} };
if ( scalar(@choices) == 2 && $choices[0] eq "0" && $choices[1] eq "1" ) {
$value = 0;
}
}
}
# force explicit protocol on OPACBaseURL
if ( $variable eq 'opacbaseurl' && $value && substr( $value, 0, 4 ) !~ /http/ ) {
@ -478,6 +489,13 @@ sub delete_preference {
sub csv_delimiter {
my ( $self, $value ) = @_;
# Validate input against allowed delimiters
if ($value) {
my %allowed = map { $_ => 1 } ALLOWED_CSV_DELIMITERS;
$value = undef unless $allowed{$value};
}
my $delimiter = $value || $self->preference('CSVDelimiter') || ',';
$delimiter = "\t" if $delimiter eq 'tabulation';
return $delimiter;
@ -572,17 +590,19 @@ sub _new_Zconn {
$syntax = 'xml';
$elementSetName = 'marcxml';
my $host = _common_config( $server, 'listen' )->{content};
my $host = _common_config( $server, 'listen' );
$host = $host->{content};
my $serverinfo = _common_config( $server, 'serverinfo' );
my $user = $serverinfo->{user};
my $password = $serverinfo->{password};
eval {
# set options
my $o = ZOOM::Options->new();
$o->option( user => $user ) if $user && $password;
$o->option( password => $password ) if $user && $password;
$o->option( async => 1 ) if $async;
$o->option( cqlfile => _common_config( $server, 'server' )->{cql2rpn} );
$o->option( user => $user ) if $user && $password;
$o->option( password => $password ) if $user && $password;
$o->option( async => 1 ) if $async;
my $server_conf = _common_config( $server, 'server' );
$o->option( cqlfile => $server_conf->{cql2rpn} );
$o->option( cclfile => $serverinfo->{ccl2rpn} );
$o->option( preferredRecordSyntax => $syntax );
$o->option( elementSetName => $elementSetName ) if $elementSetName;
@ -604,15 +624,6 @@ sub _new_Zconn {
return $Zconn;
}
# _new_dbh
# Internal helper function (not a method!). This creates a new
# database connection from the data given in the current context, and
# returns it.
sub _new_dbh {
Koha::Database->schema( { new => 1 } )->storage->dbh;
}
=head2 dbh
$dbh = C4::Context->dbh;
@ -634,26 +645,6 @@ sub dbh {
return Koha::Database->schema( { new => 1 } )->storage->dbh;
}
=head2 new_dbh
$dbh = C4::Context->new_dbh;
Creates a new connection to the Koha database for the current context,
and returns the database handle (a C<DBI::db> object).
The handle is not saved anywhere: this method is strictly a
convenience function; the point is that it knows which database to
connect to so that the caller doesn't have to know.
=cut
#'
sub new_dbh {
my $self = shift;
return &dbh( { new => 1 } );
}
=head2 userenv
C4::Context->userenv;
@ -906,7 +897,12 @@ variable to be set correctly.
sub set_remote_address {
if ( C4::Context->config('koha_trusted_proxies') ) {
require CGI;
my $header = CGI->http('HTTP_X_FORWARDED_FOR');
# If no reverse_proxy_ip_header, just use HTTP_X_FORWARDED_FOR as the header to check, simplifies logic and is a bit faster that way
my $reverse_proxy_ip_header = C4::Context->config('reverse_proxy_ip_header') || 'HTTP_X_FORWARDED_FOR';
# Check the cgi for the custom header, if the custom header is not set, fall back to HTTP_X_FORWARDED_FOR
my $header = CGI->http($reverse_proxy_ip_header) || CGI->http('HTTP_X_FORWARDED_FOR');
if ($header) {
require Koha::Middleware::RealIP;
@ -987,6 +983,26 @@ sub is_internal_PSGI_request {
return $is_internal;
}
=head3 is_opac_suppressed
return if C4::Context->is_opac_suppressed;
is_opac_suppressed returns true if and only if OPAC suppression is enabled for
the current request, based on the sysprefs OpacSuppression and
OpacSuppressionByIPRange.
=cut
sub is_opac_suppressed {
return 0 unless C4::Context->preference('OpacSuppression');
my $IPAddress = $ENV{'REMOTE_ADDR'};
my $IPRange = C4::Context->preference('OpacSuppressionByIPRange');
return 1 unless $IPRange;
return $IPAddress !~ /^$IPRange/;
}
__END__
=head1 ENVIRONMENT

View File

@ -272,7 +272,7 @@ ADD_LAYOUTS:
This function returns an arrayref whose elements are hashes containing all profiles upon success and 1 upon failure. Errors are logged
to the Apache log. Two parameters are accepted. The first limits the field(s) returned. This parameter should be string of comma separated
fields. ie. "field_1, field_2, ...field_n" The second limits the records returned based on a string containing a valud SQL 'WHERE' filter.
fields. ie. "field_1, field_2, ...field_n" The second limits the records returned based on a string containing a valid SQL 'WHERE' filter.
NOTE: Do not pass in the keyword 'WHERE.'
@ -320,7 +320,7 @@ sub get_all_image_names {
This function returns an arrayref whose elements are hashes containing the batch_ids of current batches along with the item count
for each batch upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log.
One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter.
One parameter is accepted which limits the records returned based on a string containing a valid SQL 'WHERE' filter.
NOTE: Do not pass in the keyword 'WHERE.'
@ -352,7 +352,7 @@ sub get_batch_summary {
This function returns an arrayref whose elements are hashes containing the label_ids of current labels along with the item count
for each label upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log.
One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter.
One parameter is accepted which limits the records returned based on a string containing a valid SQL 'WHERE' filter.
NOTE: Do not pass in the keyword 'WHERE.'
@ -405,7 +405,7 @@ sub get_label_summary {
This function returns an arrayref whose elements are hashes containing the label_ids of current cards along with the item count
for each card upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log.
One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter.
One parameter is accepted which limits the records returned based on a string containing a valid SQL 'WHERE' filter.
NOTE: Do not pass in the keyword 'WHERE.'

80
C4/External/Amazon.pm vendored
View File

@ -1,80 +0,0 @@
package C4::External::Amazon;
# Copyright (C) 2006 LibLime
# <jmf at liblime dot com>
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use strict;
use warnings;
use vars qw(@ISA @EXPORT);
BEGIN {
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(
get_amazon_tld
);
}
sub get_amazon_tld {
my %tld = (
CA => '.ca',
DE => '.de',
FR => '.fr',
IN => '.in',
JP => '.jp',
UK => '.co.uk',
US => '.com',
);
my $locale = C4::Context->preference('AmazonLocale');
my $tld = $tld{$locale} || '.com'; # default top level domain is .com
return $tld;
}
=head1 NAME
C4::External::Amazon - Functions for retrieving Amazon.com content in Koha
=head2 FUNCTIONS
This module provides facilities for retrieving Amazon.com content in Koha
=over
=item get_amazon_tld()
Get Amazon Top Level Domain depending on Amazon local preference: AmazonLocal.
For example, if AmazonLocal is 'UK', returns '.co.uk'.
=back
=cut
1;
__END__
=head1 NOTES
=cut
=head1 AUTHOR
Joshua Ferraro <jmf@liblime.com>
=cut

View File

@ -1,169 +0,0 @@
package C4::External::BakerTaylor;
# Copyright (C) 2008 LibLime
# <jmf at liblime dot com>
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use base 'Exporter';
BEGIN {
our @EXPORT_OK = qw(availability content_cafe_url image_url link_url http_jacket_link);
}
use XML::Simple;
use LWP::Simple qw( get );
use C4::Context;
use vars qw(%EXPORT_TAGS $VERSION);
BEGIN {
$VERSION = 3.07.00.049;
}
# These variables are plack safe: they are initialized each time
my ( $user, $pass, $agent, $image_url, $link_url );
sub _initialize {
$user = ( @_ ? shift : C4::Context->preference('BakerTaylorUsername') ) || ''; # LL17984
$pass = ( @_ ? shift : C4::Context->preference('BakerTaylorPassword') ) || ''; # CC82349
$link_url = ( @_ ? shift : C4::Context->preference('BakerTaylorBookstoreURL') );
$image_url =
"https://contentcafe2.btol.com/ContentCafe/Jacket.aspx?UserID=$user&Password=$pass&Options=Y&Return=T&Type=S&Value=";
$agent = "Koha/$VERSION [en] (Linux)";
#"Mozilla/4.76 [en] (Win98; U)", # if for some reason you want to go stealth, you might prefer this
}
sub image_url {
_initialize();
( $user and $pass ) or return;
my $isbn = ( @_ ? shift : '' );
$isbn =~ s/(p|-)//g; # sanitize
return $image_url . $isbn;
}
sub link_url {
_initialize();
my $isbn = ( @_ ? shift : '' );
$isbn =~ s/(p|-)//g; # sanitize
$link_url or return;
return $link_url . $isbn;
}
sub content_cafe_url {
_initialize();
( $user and $pass ) or return;
my $isbn = ( @_ ? shift : '' );
$isbn =~ s/(p|-)//g; # sanitize
return
"https://contentcafe2.btol.com/ContentCafeClient/ContentCafe.aspx?UserID=$user&Password=$pass&Options=Y&ItemKey=$isbn";
}
sub http_jacket_link {
_initialize();
my $isbn = shift or return;
$isbn =~ s/(p|-)//g; # sanitize
my $image = availability($isbn);
my $alt = "Buy this book";
$image and $image = qq(<img class="btjacket" alt="$alt" src="$image" />);
my $link = &link_url($isbn);
unless ($link) { return $image || ''; }
return sprintf qq(<a class="btlink" href="%s">%s</a>), $link, ( $image || $alt );
}
sub availability {
_initialize();
my $isbn = shift or return;
( $user and $pass ) or return;
$isbn =~ s/(p|-)//g; # sanitize
my $url =
"https://contentcafe2.btol.com/ContentCafe/InventoryAvailability.asmx/CheckInventory?UserID=$user&Password=$pass&Value=$isbn";
my $content = get($url);
warn "could not retrieve $url" unless $content;
my $xmlsimple = XML::Simple->new();
my $result = $xmlsimple->XMLin($content);
if ( $result->{Error} ) {
warn "Error returned to " . __PACKAGE__ . " : " . $result->{Error};
}
my $avail = $result->{Availability};
return ( $avail and $avail !~ /^false$/i ) ? &image_url($isbn) : 0;
}
1;
__END__
=head1 NAME
C4::External::BakerTaylor
=head1 DESCRIPTION
Functions for retrieving content from Baker and Taylor, inventory availability and "Content Cafe".
The settings for this module are controlled by System Preferences:
These can be overridden for testing purposes using the initialize function.
=head1 FUNCTIONS
=head2 image_url
Missing POD for image_url.
=head2 link_url
Missing POD for link_url.
=head2 content_cafe_url
Missing POD for content_cafe_url.
=head2 http_jacket_link
Missing POD for http_jacket_link.
=head2 availability($isbn);
$isbn is a isbn string
=head1 NOTES
A request with failed authentication might see this back from Baker + Taylor:
<?xml version="1.0" encoding="utf-8"?>
<InventoryAvailability xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" DateTime="2008-03-07T22:01:25.6520429-05:00" xmlns="http://ContentCafe2.btol.com">
<Key Type="Undefined">string</Key>
<Availability>false</Availability>
<Error>Invalid UserID</Error>
</InventoryAvailability>
Such response will trigger a warning for each request (potentially many). Point being, do not leave this module configured with incorrect username and password in production.
=head1 SEE ALSO
LWP::UserAgent
=head1 AUTHOR
Joe Atzberger
atz AT liblime DOT com
=cut

View File

@ -93,7 +93,7 @@ OPTION: foreach my $option (@$messaging_options) {
}
if ( $option->{'takes_days'} ) {
if ( defined $query->param( $option->{'message_attribute_id'} . '-DAYS' ) ) {
if ( $query->param( $option->{'message_attribute_id'} . '-DAYS' ) ) {
$updater->{'days_in_advance'} = $query->param( $option->{'message_attribute_id'} . '-DAYS' );
}
}
@ -135,7 +135,7 @@ PREF: foreach my $option (@$messaging_options) {
$option->{days_in_advance} = $days_in_advance;
@{ $option->{'select_days'} } = map {
{
day => $_,
day => $_ || q{},
selected => $_ == $days_in_advance
}
} ( 0 .. MAX_DAYS_IN_ADVANCE );
@ -173,7 +173,7 @@ sub restore_form_values {
$option->{days_in_advance} = $days_in_advance;
@{ $option->{'select_days'} } = map {
{
day => $_,
day => $_ || q{},
selected => $_ == $days_in_advance
}
} ( 0 .. MAX_DAYS_IN_ADVANCE );

View File

@ -48,6 +48,7 @@ use Koha::Patrons;
use List::Util qw( shuffle );
use List::MoreUtils qw( any );
use Algorithm::Munkres qw();
use Parallel::ForkManager;
=head1 FUNCTIONS
@ -438,6 +439,15 @@ sub _checkHoldPolicy {
}
=head2 _allocateWithTransportCostMatrix
_allocateWithTransportCostMatrix( $hold_requests, $available_items, $branches_to_use, $libraries,
$transport_cost_matrix, $allocated_items, $items_by_itemnumber )
given a list of holds and items attempt to fill the holds in the 'least cost' order
=cut
sub _allocateWithTransportCostMatrix {
my (
$hold_requests, $available_items, $branches_to_use, $libraries, $transport_cost_matrix, $allocated_items,
@ -466,7 +476,6 @@ sub _allocateWithTransportCostMatrix {
my @m = map { [ (undef) x $num_tasks ] } ( 1 .. $num_agents );
my $inf = -1; # Initially represent infinity with a negative value.
my $max = 0;
# If some candidate holds requests cannot be filled and there are
@ -483,7 +492,8 @@ sub _allocateWithTransportCostMatrix {
RETRY:
while (1) {
return [] if $num_agents == 0 || $num_tasks == 0;
my $inf = -1; # Initially represent infinity with a negative value.
return [] if $num_agents == 0 || $num_tasks + scalar(@remaining) == 0;
if ( $num_tasks < $num_agents && @remaining ) {
@ -658,20 +668,32 @@ RETRY:
if ( $retries-- > 0 && @unallocated && @remaining ) {
# @unallocated is built by iterating agents (rows), so its
# entries are not necessarily in column order: Munkres'
# row-to-column assignment is a permutation, not a monotonic
# mapping. The matrix compaction below scans columns in
# ascending order and expects to consume @unallocated in that
# same order, so it must be sorted first.
my @unallocated_sorted = sort { $a <=> $b } @unallocated;
# Remove the transport cost of unfilled holds and compact the matrix.
# Also remove the hold request from the array.
for ( my $i = 0 ; $i < $num_agents ; $i++ ) {
my $u = 0;
for ( my $j = 0 ; $j < $num_tasks ; $j++ ) {
if ( $u < scalar(@unallocated) && $unallocated[$u] == $j ) {
if ( $u < scalar(@unallocated_sorted) && $unallocated_sorted[$u] == $j ) {
$u++;
} elsif ( $u > 0 ) {
$m[$i][ $j - $u ] = $m[$i][$j];
}
}
}
for ( my $u = 0 ; $u < scalar(@unallocated) ; $u++ ) {
splice @requests, $unallocated[$u], 1;
# Indices must be spliced out in reverse order, otherwise removing
# a lower index shifts the position of any higher index still to
# be removed.
for my $u ( reverse @unallocated_sorted ) {
splice @requests, $u, 1;
}
$num_tasks = scalar(@requests);
@ -703,7 +725,8 @@ RETRY:
about the hold it has been mapped to.
This routine attempts to match the holds in the following priority
1 - If local holds priority is enabled we check all requests to see if local matches can be found
1 - If local holds priority is enabled, and LocalHoldsPriorityScope includes the holds queue,
we check all requests to see if local matches can be found
2 - We check for item level matches and fill those
3 - We now loop the remaining requests in priority order attempting to fill with
a - Items where HoldsQueuePrioritizeBranch matches either from items held at the pickup branch, or at the least cost branch (if Transport Cost Matrix is being used)
@ -738,8 +761,9 @@ sub MapItemsToHoldRequests {
# Look for Local Holds Priority matches first
my $LocalHoldsPriority = C4::Context->preference('LocalHoldsPriority');
if ( $LocalHoldsPriority ne 'None' ) {
my $LocalHoldsPriority = C4::Context->preference('LocalHoldsPriority');
my $LocalHoldsPriorityScope = C4::Context->preference('LocalHoldsPriorityScope');
if ( $LocalHoldsPriority ne 'None' && $LocalHoldsPriorityScope eq 'capture_and_queue' ) {
my $LocalHoldsPriorityPatronControl = C4::Context->preference('LocalHoldsPriorityPatronControl');
my $LocalHoldsPriorityItemControl = C4::Context->preference('LocalHoldsPriorityItemControl');
foreach my $request (@$hold_requests) {
@ -887,6 +911,7 @@ sub MapItemsToHoldRequests {
$item_map{ $allocation->[0] } = $allocation->[1];
$num_items_remaining--;
}
_flag_local_holdgroup_matches( \%item_map, \%items_by_itemnumber );
return \%item_map;
}
@ -898,7 +923,10 @@ sub MapItemsToHoldRequests {
push @{ $items_by_branch{ $item->{holdingbranch} } }, $item
unless exists $allocated_items{ $item->{itemnumber} };
}
return \%item_map unless keys %items_by_branch;
unless ( keys %items_by_branch ) {
_flag_local_holdgroup_matches( \%item_map, \%items_by_itemnumber );
return \%item_map;
}
# now handle the title-level requests
$num_items_remaining = scalar(@$available_items) - scalar( keys %allocated_items );
@ -1010,9 +1038,63 @@ sub MapItemsToHoldRequests {
$num_items_remaining--;
}
}
_flag_local_holdgroup_matches( \%item_map, \%items_by_itemnumber );
return \%item_map;
}
=head2 _flag_local_holdgroup_matches
_flag_local_holdgroup_matches( $item_map, $items_by_itemnumber );
For each mapped item that does not already have local_holdgroup_match set,
check whether the item and the patron belong to the same library hold group
using the LocalHoldsExclusivity* system preferences. This runs independently
of LocalHoldsPriority so that the exclusivity feature can operate on its own.
=cut
sub _flag_local_holdgroup_matches {
my ( $item_map, $items_by_itemnumber ) = @_;
my $excl_period = C4::Context->preference('LocalHoldsExclusivityPeriod');
return unless $excl_period;
my $patron_control = C4::Context->preference('LocalHoldsExclusivityPatronControl') || 'PickupLibrary';
my $item_control = C4::Context->preference('LocalHoldsExclusivityItemControl') || 'holdingbranch';
foreach my $itemnumber ( keys %$item_map ) {
my $mapped = $item_map->{$itemnumber};
next if $mapped->{local_holdgroup_match};
my $item_branchcode =
$items_by_itemnumber->{$itemnumber}
? $items_by_itemnumber->{$itemnumber}->{$item_control}
: undef;
unless ($item_branchcode) {
my $item_obj = Koha::Items->find($itemnumber);
$item_branchcode = $item_obj->$item_control if $item_obj;
}
next unless $item_branchcode;
my $patron_branchcode =
( $patron_control eq 'PickupLibrary' ) ? $mapped->{pickup_branch}
: ( $patron_control eq 'HomeLibrary' ) ? Koha::Patrons->find( $mapped->{borrowernumber} )->branchcode
: undef;
next unless $patron_branchcode;
if ( $item_branchcode eq $patron_branchcode ) {
$mapped->{local_holdgroup_match} = 1;
next;
}
my $item_library = Koha::Libraries->find( { branchcode => $item_branchcode } );
if ( $item_library && $item_library->validate_hold_sibling( { branchcode => $patron_branchcode } ) ) {
$mapped->{local_holdgroup_match} = 1;
}
}
}
=head2 _can_item_fill_request
my $bool = _can_item_fill_request( $item, $request, $libraries );
@ -1064,19 +1146,20 @@ sub CreatePicklistFromItemMap {
my $sth_load = $dbh->prepare( "
INSERT INTO tmp_holdsqueue (biblionumber,itemnumber,barcode,surname,firstname,phone,borrowernumber,
cardnumber,reservedate,title, itemcallnumber,
holdingbranch,pickbranch,notes, item_level_request)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
holdingbranch,pickbranch,notes, hold_group_id, item_level_request, local_holdgroup_match)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
" );
foreach my $itemnumber ( sort keys %$item_map ) {
my $mapped_item = $item_map->{$itemnumber};
my $biblionumber = $mapped_item->{biblionumber};
my $borrowernumber = $mapped_item->{borrowernumber};
my $pickbranch = $mapped_item->{pickup_branch};
my $holdingbranch = $mapped_item->{holdingbranch};
my $reservedate = $mapped_item->{reservedate};
my $reservenotes = $mapped_item->{reservenotes};
my $item_level = $mapped_item->{item_level};
my $mapped_item = $item_map->{$itemnumber};
my $biblionumber = $mapped_item->{biblionumber};
my $borrowernumber = $mapped_item->{borrowernumber};
my $pickbranch = $mapped_item->{pickup_branch};
my $holdingbranch = $mapped_item->{holdingbranch};
my $reservedate = $mapped_item->{reservedate};
my $reservenotes = $mapped_item->{reservenotes};
my $item_level = $mapped_item->{item_level};
my $local_holdgroup_match = $mapped_item->{local_holdgroup_match} ? 1 : 0;
my $item = Koha::Items->find($itemnumber);
my $barcode = $item->barcode;
@ -1091,10 +1174,17 @@ sub CreatePicklistFromItemMap {
my $biblio = Koha::Biblios->find($biblionumber);
my $title = $biblio->title;
my $hold_group_id;
if ( C4::Context->preference("DisplayAddHoldGroups") ) {
my $reserve_id = $mapped_item->{reserve_id};
my $hold = Koha::Holds->find($reserve_id);
$hold_group_id = $hold ? $hold->hold_group_id : undef;
}
$sth_load->execute(
$biblionumber, $itemnumber, $barcode, $surname, $firstname, $phone, $borrowernumber,
$cardnumber, $reservedate, $title, $itemcallnumber,
$holdingbranch, $pickbranch, $reservenotes, $item_level
$holdingbranch, $pickbranch, $reservenotes, $hold_group_id, $item_level, $local_holdgroup_match
);
}
}
@ -1110,7 +1200,7 @@ sub AddToHoldTargetMap {
my $schema = Koha::Database->new->schema;
my $insert_sql = q(
INSERT INTO hold_fill_targets (borrowernumber, biblionumber, itemnumber, source_branchcode, item_level_request, reserve_id) VALUES (?, ?, ?, ?, ?, ?)
INSERT INTO hold_fill_targets (borrowernumber, biblionumber, itemnumber, source_branchcode, item_level_request, reserve_id, local_holdgroup_match) VALUES (?, ?, ?, ?, ?, ?, ?)
);
my $sth_insert = $dbh->prepare($insert_sql);
@ -1121,7 +1211,8 @@ sub AddToHoldTargetMap {
$dbh->do( 'DELETE FROM hold_fill_targets WHERE itemnumber = ?', {}, $itemnumber );
$sth_insert->execute(
$mapped_item->{borrowernumber}, $mapped_item->{biblionumber}, $itemnumber,
$mapped_item->{holdingbranch}, $mapped_item->{item_level}, $mapped_item->{reserve_id}
$mapped_item->{holdingbranch}, $mapped_item->{item_level}, $mapped_item->{reserve_id},
$mapped_item->{local_holdgroup_match} ? 1 : 0,
);
}
);
@ -1130,6 +1221,12 @@ sub AddToHoldTargetMap {
# Helper functions, not part of any interface
=head2 _trim
Remove leading and trailing spaces
=cut
sub _trim {
return $_[0] unless $_[0];
$_[0] =~ s/^\s+//;
@ -1162,7 +1259,8 @@ sub load_branches_to_pull_from {
my $today = dt_from_string();
if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
@branches_to_use = grep { !Koha::Calendar->new( branchcode => $_ )->is_holiday($today) } @branches_to_use;
@branches_to_use =
grep { !Koha::Library::Calendar->new( branchcode => $_ )->is_holiday($today) } @branches_to_use;
}
return \@branches_to_use;

View File

@ -73,6 +73,7 @@ BEGIN {
use C4::Context;
use C4::Koha qw( GetNormalizedISBN );
use JSON qw( encode_json decode_json );
use C4::Biblio qw(
AddBiblio
DelBiblio
@ -1547,7 +1548,7 @@ sub GetImportRecordMatches {
# FIXME currently biblio only
my $sth = $dbh->prepare_cached(
"SELECT title, author, biblionumber,
candidate_match_id, score, record_type,
candidate_match_id, score, composite_scores, record_type,
chosen
FROM import_records
JOIN import_record_matches USING (import_record_id)
@ -1563,6 +1564,10 @@ sub GetImportRecordMatches {
$row->{'authorized_heading'} = GetAuthorizedHeading( { authid => $row->{'candidate_match_id'} } );
}
next if ( $row->{'record_type'} eq 'biblio' && not $row->{'biblionumber'} );
$row->{'composite_scores'} =
$row->{'composite_scores'}
? decode_json( $row->{'composite_scores'} )
: {};
push @$results, $row;
last if $best_only;
}
@ -1588,12 +1593,16 @@ sub SetImportRecordMatches {
$delsth->finish();
my $sth = $dbh->prepare(
"INSERT INTO import_record_matches (import_record_id, candidate_match_id, score, chosen)
VALUES (?, ?, ?, ?)"
"INSERT INTO import_record_matches (import_record_id, candidate_match_id, score, composite_scores, chosen)
VALUES (?, ?, ?, ?, ?)"
);
my $chosen = 1; #The first match is defaulted to be chosen
foreach my $match (@matches) {
$sth->execute( $import_record_id, $match->{'record_id'}, $match->{'score'}, $chosen );
my $composite_json =
$match->{'composite_scores'}
? encode_json( $match->{'composite_scores'} )
: undef;
$sth->execute( $import_record_id, $match->{'record_id'}, $match->{'score'}, $composite_json, $chosen );
$chosen = 0; #After the first we do not default to other matches
}
}
@ -1710,6 +1719,16 @@ sub RecordsFromMarcPlugin {
# internal functions
=head2 _create_import_record
my $import_record_id = _create_import_record($batch_id, $record_sequence,
$marc_record, $record_type, $encoding, $marc_type);
Inserts a new row into C<import_records> for the given MARC record and returns
the new C<import_record_id>.
=cut
sub _create_import_record {
my ( $batch_id, $record_sequence, $marc_record, $record_type, $encoding, $marc_type ) = @_;
@ -1728,6 +1747,15 @@ sub _create_import_record {
return $import_record_id;
}
=head2 _add_auth_fields
_add_auth_fields($import_record_id, $marc_record);
Extracts authority-specific fields from C<$marc_record> and stores them
in C<import_auths>.
=cut
sub _add_auth_fields {
my ( $import_record_id, $marc_record ) = @_;
@ -1743,6 +1771,15 @@ sub _add_auth_fields {
$sth->finish();
}
=head2 _add_biblio_fields
_add_biblio_fields($import_record_id, $marc_record);
Extracts bibliographic fields (title, author, ISBN) from C<$marc_record> and
inserts them into C<import_biblios>.
=cut
sub _add_biblio_fields {
my ( $import_record_id, $marc_record ) = @_;
@ -1758,6 +1795,15 @@ sub _add_biblio_fields {
}
=head2 _update_biblio_fields
_update_biblio_fields($import_record_id, $marc_record);
Updates the C<import_biblios> row for an already-staged bibliographic record
with freshly parsed field values.
=cut
sub _update_biblio_fields {
my ( $import_record_id, $marc_record ) = @_;
@ -1777,6 +1823,16 @@ sub _update_biblio_fields {
$sth->finish();
}
=head2 _parse_biblio_fields
my ($title, $author, $isbn, $issn, $lccn, $biblionumber) =
_parse_biblio_fields($marc_record);
Extracts key bibliographic fields from a MARC::Record object and returns
them as a list.
=cut
sub _parse_biblio_fields {
my ($marc_record) = @_;
@ -1791,6 +1847,15 @@ sub _parse_biblio_fields {
}
=head2 _update_batch_record_counts
_update_batch_record_counts($batch_id);
Recalculates and updates the record-count summary columns on the
C<import_batches> row for C<$batch_id>.
=cut
sub _update_batch_record_counts {
my ($batch_id) = @_;
@ -1819,6 +1884,18 @@ sub _update_batch_record_counts {
);
}
=head2 _get_commit_action
my ($biblio_action, $item_action, $match_id) =
_get_commit_action($overlay_action, $nomatch_action, $item_action,
$overlay_status, $import_record_id, $record_type);
Determines the appropriate commit action (C<create_new>, C<replace>,
C<ignore>) for a record based on the batch overlay/nomatch settings and
the record's current overlay status.
=cut
sub _get_commit_action {
my ( $overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id, $record_type ) = @_;
@ -1859,6 +1936,16 @@ sub _get_commit_action {
}
}
=head2 _get_revert_action
my ($bib_result, $item_result) =
_get_revert_action($overlay_action, $overlay_status, $status);
Determines the appropriate revert action for a record based on the batch
overlay action and the record's current overlay/import status.
=cut
sub _get_revert_action {
my ( $overlay_action, $overlay_status, $status ) = @_;

View File

@ -111,7 +111,7 @@ sub get_template_and_user {
my $template = C4::Templates->new( $interface, $filename, $tmplbase, $query );
my $request_method = $in->{query}->request_method // q{};
unless ( $request_method eq 'POST' && $in->{query}->param('op') eq 'cud-login' ) {
unless ( $request_method eq 'POST' && $in->{query}->param('login_op') eq 'cud-login' ) {
$in->{query}->param( 'login_userid', '' );
$in->{query}->param( 'login_password', '' );
}
@ -236,7 +236,6 @@ sub checkauth {
my $type = shift;
$type = 'intranet' unless $type;
my $dbh = C4::Context->dbh();
my $template_name;
$template_name = "installer/auth.tt";
@ -352,7 +351,7 @@ sub checkauth {
# else we have a problem...
# get the inputs from the incoming query
my @inputs = ();
foreach my $name ( param $query) {
foreach my $name ( param $query ) {
(next) if ( $name eq 'userid' || $name eq 'password' );
my $value = $query->param($name);
push @inputs, { name => $name, value => $value };

View File

@ -388,6 +388,7 @@ sub load_sql_in_order {
push @fnames, "$global_mandatory_dir/userflags.sql",
"$global_mandatory_dir/userpermissions.sql",
"$global_mandatory_dir/audio_alerts.sql",
"$global_mandatory_dir/record_sources.sql",
;
my $localization_file =
C4::Context->config('intranetdir') . "/installer/data/$self->{dbms}/localization/$langchoice/custom.sql";
@ -460,10 +461,8 @@ sub set_marcflavour_syspref {
# marc_cleaned finds the marcflavour, without the variant.
my $marc_cleaned = 'MARC21';
$marc_cleaned = 'UNIMARC' if $marcflavour =~ /unimarc/i;
my $request =
$self->{'dbh'}->prepare(
"INSERT IGNORE INTO `systempreferences` (variable,value,explanation,options,type) VALUES('marcflavour','$marc_cleaned','Define global MARC flavor (MARC21 or UNIMARC) used for character encoding','MARC21|UNIMARC','Choice');"
);
my $request = $self->{'dbh'}
->prepare("INSERT IGNORE INTO `systempreferences` (variable, value) VALUES('marcflavour', '$marc_cleaned')");
$request->execute;
}
@ -754,14 +753,8 @@ Missing POD for TableExists.
sub TableExists { # Could be renamed table_exists for consistency
my $table = shift;
eval {
my $dbh = C4::Context->dbh;
local $dbh->{PrintError} = 0;
local $dbh->{RaiseError} = 1;
$dbh->do(qq{SELECT * FROM $table WHERE 1 = 0 });
};
return 1 unless $@;
return 0;
require Koha::Database;
return Koha::Database->table_exists( C4::Context->dbh, $table );
}
=head2 version_from_file

View File

@ -38,6 +38,7 @@ BEGIN {
SearchItems
PrepareItemrecordDisplay
ToggleNewStatus
GetAutomaticItemModificationAgeFields
);
}
@ -58,6 +59,7 @@ use Try::Tiny qw( catch try );
use Koha::AuthorisedValues;
use Koha::DateUtils qw( dt_from_string );
use Koha::Database;
use Koha::Logger;
use Koha::Biblios;
use Koha::Biblioitems;
@ -599,7 +601,7 @@ sub GetItemsForInventory {
my $max_cnsort = GetClassSort( $class_source, undef, $maxlocation );
my $select_columns = q{
SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode
SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode, enumchron
};
my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
@ -1655,6 +1657,25 @@ sub PrepareItemrecordDisplay {
};
}
=head2 GetAutomaticItemModificationAgeFields
my @fields = GetAutomaticItemModificationAgeFields();
Returns the fixed, C<items.>-prefixed list of date columns that are valid
"age field" values for automatic item modification rules (see
ToggleNewStatus below). This is the single source of truth for that
allow-list; it is shared with F<tools/automatic_item_modification_by_age.pl>
so the two cannot drift apart.
=cut
sub GetAutomaticItemModificationAgeFields {
return (
'items.dateaccessioned', 'items.replacementpricedate', 'items.datelastborrowed', 'items.datelastseen',
'items.damaged_on', 'items.itemlost_on', 'items.withdrawn_on'
);
}
=head2 ToggleNewStatus
Missing POD for ToggleNewStatus.
@ -1671,17 +1692,32 @@ sub ToggleNewStatus {
my @item_columns = map { "items.$_" } Koha::Items->columns;
my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
my @biblio_columns = map { "biblio.$_" } Koha::Biblios->columns;
my @age_fields = GetAutomaticItemModificationAgeFields();
my $report;
for my $rule (@rules) {
my $age = $rule->{age};
# Default to using items.dateaccessioned if there's an old item modification rule
# missing an agefield value
my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned';
# missing an agefield value, or if the stored value is not (or is no longer) a
# recognised age field - values are only trusted from a fixed allow-list since
# this is spliced directly into the SQL below and cannot be bound as a placeholder.
my $agefield = $rule->{agefield} // '';
unless ( grep { $_ eq $agefield } @age_fields ) {
Koha::Logger->get->warn(qq{Rejecting invalid agefield "$agefield" in automatic item modification rule})
if length $agefield;
$agefield = 'items.dateaccessioned';
}
my $conditions = $rule->{conditions};
my $substitutions = $rule->{substitutions};
foreach (@$substitutions) {
( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
foreach my $substitution (@$substitutions) {
my $field = $substitution->{field} // '';
if ( grep { $_ eq $field } @item_columns ) {
( $substitution->{item_field} ) = ( $field =~ /^items\.(.*)/ );
} else {
Koha::Logger->get->warn(
qq{Rejecting invalid substitution field "$field" in automatic item modification rule})
if length $field;
}
}
my @params;
@ -1706,6 +1742,9 @@ sub ToggleNewStatus {
$query .= qq| AND $condition->{field} = ?|;
push @params, $condition->{value};
}
} else {
Koha::Logger->get->warn(
qq{Rejecting invalid condition field "$condition->{field}" in automatic item modification rule});
}
}
if ( defined $age ) {
@ -1722,6 +1761,7 @@ sub ToggleNewStatus {
my $field = $substitution->{item_field};
my $value = $substitution->{value};
next unless $substitution->{field};
next unless defined $field;
next
if ( defined $values->{ $substitution->{item_field} }
and $values->{ $substitution->{item_field} } eq $substitution->{value} );

View File

@ -33,10 +33,7 @@ BEGIN {
getitemtypeimagesrc
getitemtypeimagelocation
GetAuthorisedValues
GetNormalizedUPC
GetNormalizedISBN
GetNormalizedEAN
GetNormalizedOCLCNumber
xml_escape
GetVariationsOfISBN
@ -508,37 +505,51 @@ C<$opac> If set to a true value, displays OPAC descriptions rather than normal o
sub GetAuthorisedValues {
my $category = shift // ''; # optional parameter
my $opac = shift ? 1 : 0; # normalise to be safe
my $options = shift // '';
my $no_limit;
if ($options) { $no_limit = $options->{'no_limit'}; } #optional parameter to ignore library limitation
$no_limit = $no_limit ? 1 : 0;
# Is this cached already?
my $branch_limit = C4::Context::mybranch();
my $cache_key = "AuthorisedValues-$category-$opac-$branch_limit";
my $cache_key = "AuthorisedValues-$category-$opac-$branch_limit-$no_limit";
my $cache = Koha::Caches->get_instance();
my $result = $cache->get_from_cache($cache_key);
return $result if $result;
my @results;
my $dbh = C4::Context->dbh;
my $dbh = C4::Context->dbh;
my $select_clause = 'SELECT av.*';
my @where_args;
if ( $branch_limit && $no_limit ) {
$select_clause .= ', MIN(IF (branchcode = ? OR branchcode IS NULL, 0, 1)) as restricted';
push @where_args, $branch_limit;
}
my $query = qq{
SELECT DISTINCT av.*
$select_clause
FROM authorised_values av
};
$query .= qq{
LEFT JOIN authorised_values_branches ON ( id = av_id )
} if $branch_limit;
my @where_strings;
my @where_args;
if ($category) {
push @where_strings, "category = ?";
push @where_args, $category;
}
if ($branch_limit) {
if ( $branch_limit && !$no_limit ) {
push @where_strings, "( branchcode = ? OR branchcode IS NULL )";
push @where_args, $branch_limit;
}
if ( @where_strings > 0 ) {
$query .= " WHERE " . join( " AND ", @where_strings );
}
$query .= ' GROUP BY av.id, av.category, av.authorised_value, av.lib, av.lib_opac, av.imageurl'
if $branch_limit && $no_limit;
$query .= ' ORDER BY category, '
. (
$opac
@ -601,39 +612,6 @@ sub display_marc_indicators {
return $indicators;
}
=head2 GetNormalizedUPC
Missing POD for GetNormalizedUPC.
=cut
sub GetNormalizedUPC {
my ( $marcrecord, $marcflavour ) = @_;
$marcflavour ||= C4::Context->preference('marcflavour');
return unless $marcrecord;
if ( $marcflavour eq 'UNIMARC' ) {
my @fields = $marcrecord->field('072');
foreach my $field (@fields) {
my $upc = _normalize_match_point( $field->subfield('a') );
if ($upc) {
return $upc;
}
}
} else { # assume marc21 if not unimarc
my @fields = $marcrecord->field('024');
foreach my $field (@fields) {
my $indicator = $field->indicator(1);
my $upc = _normalize_match_point( $field->subfield('a') );
if ( $upc && $indicator eq '1' ) {
return $upc;
}
}
}
}
# Normalizes and returns the first valid ISBN found in the record
# ISBN13 are converted into ISBN10. This is required to get some book cover images.
@ -674,64 +652,6 @@ sub GetNormalizedISBN {
}
}
=head2 GetNormalizedEAN
Missing POD for GetNormalizedEAN.
=cut
sub GetNormalizedEAN {
my ( $marcrecord, $marcflavour ) = @_;
return unless $marcrecord;
if ( $marcflavour eq 'UNIMARC' ) {
my @fields = $marcrecord->field('073');
foreach my $field (@fields) {
my $ean = _normalize_match_point( $field->subfield('a') );
if ($ean) {
return $ean;
}
}
} else { # assume marc21 if not unimarc
my @fields = $marcrecord->field('024');
foreach my $field (@fields) {
my $indicator = $field->indicator(1);
my $ean = _normalize_match_point( $field->subfield('a') );
if ( $ean && $indicator eq '3' ) {
return $ean;
}
}
}
}
=head2 GetNormalizedOCLCNumber
Missing POD for GetNormalizedOCLCNumber.
=cut
sub GetNormalizedOCLCNumber {
my ( $marcrecord, $marcflavour ) = @_;
return unless $marcrecord;
$marcflavour ||= C4::Context->preference('marcflavour');
if ( $marcflavour ne 'UNIMARC' ) {
my @fields = $marcrecord->field('035');
foreach my $field (@fields) {
my $oclc = $field->subfield('a');
if ( $oclc && $oclc =~ /OCoLC/ ) {
$oclc =~ s/\(OCoLC\)//;
return $oclc;
}
}
} else {
# TODO for UNIMARC
}
}
sub _normalize_match_point {
my $match_point = shift;
( my $normalized_match_point ) = $match_point =~ /([\d-]*[X]*)/;
@ -742,13 +662,28 @@ sub _normalize_match_point {
sub _isbn_cleanup {
my ($isbn) = @_;
return NormalizeISBN(
{
isbn => $isbn,
format => 'ISBN-10',
strip_hyphens => 1,
}
) if $isbn;
return unless $isbn;
if ( $isbn =~ /^979/ ) {
# ISBNs starting with 979 cannot be converted to ISBN-10 format, but all other valid ISBNs can
return NormalizeISBN(
{
isbn => $isbn,
format => 'ISBN-13',
strip_hyphens => 1,
}
);
} else {
return NormalizeISBN(
{
isbn => $isbn,
format => 'ISBN-10',
strip_hyphens => 1,
}
);
}
}
=head2 NormalizeISBN

View File

@ -34,7 +34,7 @@ BEGIN {
use Carp qw( carp );
use CGI;
use List::MoreUtils qw( any );
use List::MoreUtils qw( any uniq );
use C4::Context;
use Koha::Caches;
use Koha::Cache::Memory::Lite;
@ -123,6 +123,10 @@ sub getTranslatedLanguages {
} elsif ( $interface eq 'opac' ) {
@enabled_languages = split ",", C4::Context->preference('OPACLanguages');
}
} else {
@enabled_languages = split ",",
join( ',', C4::Context->preference( 'StaffInterfaceLanguages', C4::Context->preference('OPACLanguages') ) );
@enabled_languages = uniq @enabled_languages;
}
my $cache = Koha::Caches->get_instance;
@ -152,9 +156,7 @@ sub getTranslatedLanguages {
$htdocs = C4::Context->config('opachtdocs');
push @languages, _get_opac_language_dirs($htdocs);
my %seen;
$seen{$_}++ for @languages;
@languages = keys %seen;
@languages = uniq @languages;
}
return _build_languages_arrayref( \@languages, $current_language, \@enabled_languages );
}
@ -369,14 +371,16 @@ FIXME: this could be rewritten and simplified using map
sub _build_languages_arrayref {
my ( $translated_languages, $current_language, $enabled_languages ) = @_;
$current_language //= '';
my @translated_languages = @$translated_languages;
my @languages_loop; # the final reference to an array of hashrefs
my @enabled_languages = @$enabled_languages;
# how many languages are enabled, if one, take note, some contexts won't need to display it
my $language_groups;
my $track_language_groups;
my $language_groups = {};
my $track_language_groups = {};
my $current_language_regex = regex_lang_subtags($current_language);
# Loop through the translated languages
@ -443,9 +447,15 @@ sub _build_languages_arrayref {
}
}
}
( exists $idx{$aa} and exists $idx{$bb} and ( $idx{$aa} cmp $idx{$bb} ) )
|| ( exists $idx{$aa} and exists $idx{$bb} )
|| exists $idx{$bb}
if ( exists $idx{$aa} and exists $idx{$bb} ) {
return $idx{$aa} cmp $idx{$bb};
} elsif ( exists $idx{$aa} ) {
return -1;
} elsif ( exists $idx{$bb} ) {
return 1;
} else {
return $a cmp $b; # Finally order by lang code
}
} keys %$language_groups;
for my $key (@ordered_keys) {
@ -480,6 +490,13 @@ Missing POD for language_get_description.
sub language_get_description {
my ( $script, $lang, $type ) = @_;
my $script_key = $script || "undef";
my $memory_cache = Koha::Cache::Memory::Lite->get_instance;
my $cache_key = "Language_descriptions:$script_key:$lang:$type";
my $cached = $memory_cache->get_from_cache($cache_key);
return $cached if $cached;
my $dbh = C4::Context->dbh;
my $desc;
my $sth = $dbh->prepare("SELECT description FROM language_descriptions WHERE subtag=? AND lang=? AND type=?");
@ -496,6 +513,7 @@ sub language_get_description {
$desc = $descriptions->{'description'};
}
}
$memory_cache->set_in_cache( $cache_key, $desc );
return $desc;
}
@ -797,11 +815,17 @@ sub getlanguage {
sub get_rfc4646_from_iso639 {
my $iso_code = shift;
my $iso_code = shift;
my $memory_cache = Koha::Cache::Memory::Lite->get_instance;
my $cache_key = "RFC4646ByISO639:$iso_code";
my $cached = $memory_cache->get_from_cache($cache_key);
return $cached if $cached;
my $rfc_subtag =
Koha::Database->new()->schema->resultset('LanguageRfc4646ToIso639')->find( { iso639_2_code => $iso_code } );
if ($rfc_subtag) {
return $rfc_subtag->rfc4646_subtag;
my $subtag = $rfc_subtag->rfc4646_subtag;
$memory_cache->set_in_cache( $cache_key, $subtag );
return $subtag;
} else {
return;
}

View File

@ -214,8 +214,8 @@ sub GetLettersAvailableForALibrary {
}
return [
map { $letters{$_} }
sort { $letters{$a}->{name} cmp $letters{$b}->{name} }
map { $letters{$_} }
sort { $letters{$a}->{name} cmp $letters{$b}->{name} }
keys %letters
];
@ -338,7 +338,7 @@ sub SendAlerts {
);
if ( $letter->{is_html} ) {
$mail->html_body( _wrap_html( $letter->{content}, "" . $letter->{title} ) );
$mail->html_body( _wrap_html( $letter->{content}, "" . $letter->{title}, 'email' ) );
} else {
$mail->text_body( $letter->{content} );
}
@ -510,7 +510,7 @@ sub SendAlerts {
);
if ( $letter->{is_html} ) {
$mail->html_body( _wrap_html( $letter->{content}, "" . $letter->{title} ) );
$mail->html_body( _wrap_html( $letter->{content}, "" . $letter->{title}, 'email' ) );
} else {
$mail->text_body( "" . $letter->{content} );
}
@ -620,24 +620,19 @@ sub GetPreparedLetter {
}
# add plugin-generated objects
if ( C4::Context->config("enable_plugins") ) {
my @plugins = Koha::Plugins->new->GetPlugins(
{
method => 'notices_content',
}
);
my @plugins = Koha::Plugins->get_enabled_plugins();
@plugins = grep { $_->can('notices_content') } @plugins;
foreach my $plugin (@plugins) {
my $namespace = $plugin->get_metadata()->{namespace};
if ($namespace) {
try {
if ( my $content = $plugin->notices_content( \%params ) ) {
$objects->{plugin_content}->{$namespace} = $content;
}
} catch {
next;
};
}
foreach my $plugin (@plugins) {
my $namespace = $plugin->get_metadata()->{namespace};
if ($namespace) {
try {
if ( my $content = $plugin->notices_content( \%params ) ) {
$objects->{plugin_content}->{$namespace} = $content;
}
} catch {
next;
};
}
}
@ -753,6 +748,14 @@ sub GetPreparedLetter {
return $letter;
}
=head2 _substitute_tables
_substitute_tables( $letter, $tables );
Substitute placeholders in the letter with values from the given tables.
=cut
sub _substitute_tables {
my ( $letter, $tables ) = @_;
while ( my ( $table, $param ) = each %$tables ) {
@ -780,6 +783,14 @@ sub _substitute_tables {
}
}
=head2 _parseletter_sth
my $sth = _parseletter_sth( $table );
Return a prepared statement handle for retrieving values from the given table.
=cut
sub _parseletter_sth {
my $table = shift;
my $sth;
@ -1021,6 +1032,7 @@ sub EnqueueLetter {
message_id => $id,
borrowernumber => $who_letter_is_for,
letter_code => $letter_code, # can be scalar or arrayref
exclude_letter_code => $exclude_letter_code, # can be scalar or arrayref
type => $type, # can be scalar or arrayref
limit => 50,
verbose => 1,
@ -1033,6 +1045,10 @@ The (optional) message_id, borrowernumber, letter_code, type and where
parameter are used to select which pending messages will be processed. The
limit parameter determines the volume of results, i.e. sent messages.
The optional exclude_letter_code parameter processes all pending messages
except those with the given letter code(s). It cannot be combined with
letter_code; passing both raises a Koha::Exceptions::BadParameter exception.
The optional verbose parameter can be used to generate debugging output.
Returns number of messages sent.
@ -1047,6 +1063,18 @@ sub SendQueuedMessages {
Koha::Exceptions::BadParameter->throw("Parameter message_id cannot be empty if passed.")
if ( exists( $params->{message_id} ) && !$params->{message_id} );
my $has_letter_code =
ref( $params->{letter_code} ) ? scalar @{ $params->{letter_code} } : $params->{letter_code};
my $has_exclude_letter_code =
ref( $params->{exclude_letter_code} )
? scalar @{ $params->{exclude_letter_code} }
: $params->{exclude_letter_code};
# letter_code and exclude_letter_code build the same search key ('letter_code'); passing both
# would let one silently clobber the other in the hash literal built below.
Koha::Exceptions::BadParameter->throw("Parameters letter_code and exclude_letter_code are mutually exclusive.")
if ( $has_letter_code && $has_exclude_letter_code );
if ( C4::Context->config("enable_plugins") ) {
my @plugins = Koha::Plugins->new->GetPlugins(
{
@ -1073,10 +1101,17 @@ sub SendQueuedMessages {
$params->{message_id} ? ( message_id => $params->{message_id} ) : (),
$params->{borrowernumber} ? ( borrowernumber => $params->{borrowernumber} ) : (),
# Check for scalar or array in letter_code and type
ref( $params->{letter_code} )
&& @{ $params->{letter_code} } ? ( letter_code => $params->{letter_code} ) : (),
!ref( $params->{letter_code} ) && $params->{letter_code} ? ( letter_code => $params->{letter_code} ) : (),
# letter_code and exclude_letter_code can each be scalar or arrayref; guaranteed
# mutually exclusive above, so they never both contribute a letter_code key here.
$has_letter_code ? ( letter_code => $params->{letter_code} ) : (),
$has_exclude_letter_code
? (
letter_code => ref( $params->{exclude_letter_code} )
? { '-not_in' => $params->{exclude_letter_code} }
: { '!=' => $params->{exclude_letter_code} }
)
: (),
ref( $params->{type} )
&& @{ $params->{type} }
? ( message_transport_type => $params->{type} )
@ -1211,7 +1246,7 @@ sub GetQueuedMessages {
my $params = shift;
my $dbh = C4::Context->dbh();
my $statement = << 'ENDSQL';
my $statement = <<'ENDSQL';
SELECT message_id, borrowernumber, subject, content, message_transport_type, status, time_queued, updated_on, failure_code, from_address, to_address, cc_address
FROM message_queue
ENDSQL
@ -1311,7 +1346,7 @@ sub ResendMessage {
return $rv;
}
=head2 _add_attachements
=head2 _add_attachments
_add_attachments({ letter => $letter, attachments => $attachments });
@ -1340,7 +1375,7 @@ sub _add_attachments {
my $message = Koha::Email->new;
if ( $letter->{is_html} ) {
$message->html_body( _wrap_html( $letter->{content}, $letter->{title} ) );
$message->html_body( _wrap_html( $letter->{content}, $letter->{title}, 'email' ) );
} else {
$message->text_body( $letter->{content} );
}
@ -1433,6 +1468,15 @@ sub _get_unsent_messages {
return $sth->fetchall_arrayref( {} );
}
=head2 _send_message_by_email
_send_message_by_email( $message, $username, $password, $method, $smtp_transports );
Send a queued message via email. Handles SMTP transport selection, attachments,
guarantor copies, and message status updates.
=cut
sub _send_message_by_email {
my $message = shift or return;
my ( $username, $password, $method, $smtp_transports ) = @_;
@ -1569,7 +1613,7 @@ sub _send_message_by_email {
} else {
$email = Koha::Email->create($params);
if ($is_html) {
$email->html_body( _wrap_html( $content, $subject ) );
$email->html_body( _wrap_html( $content, $subject, 'email' ) );
} else {
$email->text_body($content);
}
@ -1650,11 +1694,36 @@ sub _send_message_by_email {
};
}
sub _wrap_html {
my ( $content, $title ) = @_;
=head2 _wrap_html
my $css = C4::Context->preference("NoticeCSS") || '';
$css = qq{<link rel="stylesheet" type="text/css" href="$css">} if $css;
my $html = _wrap_html( $content, $title, $type );
Wrap letter content in an HTML document with appropriate stylesheets based
on the message type ('email' or 'print').
=cut
sub _wrap_html {
my ( $content, $title, $type ) = @_;
my $stylesheets = '';
my $all_stylesheets = C4::Context->preference("AllNoticeStylesheet") || '';
$stylesheets .= qq{<link rel="stylesheet" type="text/css" href="$all_stylesheets">} if $all_stylesheets;
my $all_style_pref = C4::Context->preference("AllNoticeCSS");
$stylesheets .= qq{<style type="text/css">$all_style_pref</style>} if $all_style_pref;
if ( $type eq 'email' ) {
my $email_stylesheet = C4::Context->preference("EmailNoticeStylesheet") || '';
$stylesheets .= qq{<link rel="stylesheet" type="text/css" href="$email_stylesheet">} if $email_stylesheet;
my $email_style_pref = C4::Context->preference("EmailNoticeCSS");
$stylesheets .= qq{<style type="text/css">$email_style_pref</style>} if $email_style_pref;
}
if ( $type eq 'print' ) {
my $print_stylesheet = C4::Context->preference("PrintNoticeStylesheet") || '';
$stylesheets .= qq{<link rel="stylesheet" type="text/css" href="$print_stylesheet">} if $print_stylesheet;
my $print_style_pref = C4::Context->preference("PrintNoticeCSS");
$stylesheets .= qq{<style type="text/css">$print_style_pref</style>} if $print_style_pref;
}
$stylesheets =~ s/\n+\z//;
return <<EOS;
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -1662,7 +1731,7 @@ sub _wrap_html {
<head>
<title>$title</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
$css
$stylesheets
</head>
<body>
$content
@ -1671,6 +1740,15 @@ $content
EOS
}
=head2 _is_duplicate
my $is_dup = _is_duplicate( $message );
Check whether an identical message has already been sent today for the same
patron, transport type, and letter code.
=cut
sub _is_duplicate {
my ($message) = @_;
my $dbh = C4::Context->dbh;
@ -1689,6 +1767,14 @@ sub _is_duplicate {
return $count;
}
=head2 _send_message_by_sms
_send_message_by_sms( $message );
Send a queued message via SMS using the patron's SMS alert number.
=cut
sub _send_message_by_sms {
my $message = shift or return;
my $patron = Koha::Patrons->find( $message->{borrowernumber} );
@ -1744,24 +1830,56 @@ sub _send_message_by_sms {
return $success;
}
=head2 _update_message_to_address
_update_message_to_address( $message_id, $to_address );
Update the to_address field for a queued message.
=cut
sub _update_message_to_address {
my ( $id, $to ) = @_;
my $dbh = C4::Context->dbh();
$dbh->do( 'UPDATE message_queue SET to_address=? WHERE message_id=?', undef, ( $to, $id ) );
}
=head2 _update_message_from_address
_update_message_from_address( $message_id, $from_address );
Update the from_address field for a queued message.
=cut
sub _update_message_from_address {
my ( $message_id, $from_address ) = @_;
my $dbh = C4::Context->dbh();
$dbh->do( 'UPDATE message_queue SET from_address = ? WHERE message_id = ?', undef, ( $from_address, $message_id ) );
}
=head2 _update_message_cc_address
_update_message_cc_address( $message_id, $cc_address );
Update the cc_address field for a queued message.
=cut
sub _update_message_cc_address {
my ( $message_id, $cc_address ) = @_;
my $dbh = C4::Context->dbh();
$dbh->do( 'UPDATE message_queue SET cc_address = ? WHERE message_id = ?', undef, ( $cc_address, $message_id ) );
}
=head2 _set_message_status
_set_message_status({ message_id => $id, status => $status, failure_code => $code });
Update the status and optional failure_code for a queued message.
=cut
sub _set_message_status {
my $params = shift or return;
@ -1780,6 +1898,23 @@ sub _set_message_status {
return $result;
}
=head2 _process_tt
my $output = _process_tt({
content => $content,
tables => $tables,
loops => $loops,
objects => $objects,
substitute => $substitute,
lang => $lang,
});
Process Template Toolkit content with the given parameters, returning the
rendered output. Runs inside a database transaction that is rolled back
after processing.
=cut
sub _process_tt {
my ($params) = @_;
@ -1802,8 +1937,14 @@ sub _process_tt {
my $use_template_cache = C4::Context->config('template_cache_dir') && defined $ENV{GATEWAY_INTERFACE};
my $template = Template->new(
{
EVAL_PERL => 0,
ABSOLUTE => 0,
EVAL_PERL => 0,
ABSOLUTE => 0,
RELATIVE => 0,
PLUGINS => {
cgi => 'Koha::Template::Plugin::noop',
directory => 'Koha::Template::Plugin::noop',
file => 'Koha::Template::Plugin::noop',
},
PLUGIN_BASE => 'Koha::Template::Plugin',
COMPILE_EXT => $use_template_cache ? '.ttc' : '',
COMPILE_DIR => $use_template_cache ? C4::Context->config('template_cache_dir') : '',
@ -1832,6 +1973,16 @@ sub _process_tt {
return $output;
}
=head2 _get_tt_params
my $params = _get_tt_params( $tables, $is_a_loop );
Build a hashref of Template Toolkit parameters from the given table
specifications. Each table entry is resolved to its corresponding
Koha::Object(s) for use in notice templates.
=cut
sub _get_tt_params {
my ( $tables, $is_a_loop ) = @_;

View File

@ -38,7 +38,7 @@ use vars qw(@ISA @EXPORT);
BEGIN {
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(logaction cronlogaction);
@EXPORT = qw(logaction cronlogaction logscriptaction);
}
=head1 NAME
@ -94,6 +94,17 @@ sub logaction {
}
} else {
$updated = $infos;
my %record_log_prefix = (
CATALOGUING => 'biblio',
AUTHORITIES => 'authority',
);
if ( $record_log_prefix{$modulename}
&& ref $original eq 'HASH'
&& $actionname ne 'ADD' )
{
my $prefix = ref $infos eq 'HASH' ? $record_log_prefix{$modulename} : $infos;
$infos = "$prefix " . to_json( $original, { utf8 => 1, pretty => 1 } );
}
}
my $script =
@ -123,13 +134,28 @@ sub logaction {
if ( $actionname =~ /^(ADD|CREATE)$/ ) {
# Log diff against empty hashref for newly created objects
$updated = $is_object ? $original->unblessed : $original;
$updated = $is_object ? $original->unblessed : $original;
for my $key ( keys %{$updated} ) {
if ( defined $updated->{$key} && $updated->{$key} ne '' ) {
$updated->{$key} = "$updated->{$key}" if blessed( $updated->{$key} );
} else {
delete $updated->{$key};
}
}
$original = {};
} elsif ( $actionname eq 'DELETE' ) {
# Log diff for deleted objects against empty hashref
$original = $is_object ? $original->unblessed : $original;
$updated = {};
for my $key ( keys %{$original} ) {
if ( defined $original->{$key} && $original->{$key} ne '' ) {
$original->{$key} = "$original->{$key}" if blessed( $original->{$key} );
} else {
delete $original->{$key};
}
}
$updated = {};
} else {
# Log diff against hashref of pre-modified object if passed in
@ -140,7 +166,9 @@ sub logaction {
$diff //= encode_json( diff( $original, $updated, noU => 1 ) )
if $original && ref $updated eq 'HASH';
Koha::ActionLog->new(
$infos = encode_json($infos) if ( ref $infos eq 'HASH' );
my $action_log = Koha::ActionLog->new(
{
timestamp => \'NOW()',
user => $usernumber,
@ -174,6 +202,8 @@ sub logaction {
);
}
);
return $action_log;
}
=item cronlogaction
@ -185,17 +215,41 @@ Logs the path and name of the calling script plus the information provided by pa
=cut
#'
sub cronlogaction {
my $params = shift;
my $info = $params->{info};
my $action = $params->{action};
$action ||= "Run";
my $loginfo = ( caller(0) )[1];
# $0 is a special Perl variable containing the program that is currently running
# https://perldoc.perl.org/variables/$0
my $loginfo = $0;
$loginfo .= ' ' . $info if $info;
logaction( 'CRONJOBS', $action, $$, $loginfo ) if C4::Context->preference('CronjobLog');
}
=item logscriptaction
&logscriptaction({ info => $info, action => $action });
Convenience routine to add a record into the action_logs table for a
non-cron Koha script. Logs the path of the running script plus any
additional information provided in C<info>. Only logs when the
C<ScriptLog> system preference is enabled.
=cut
sub logscriptaction {
my $params = shift;
my $info = $params->{info};
my $action = $params->{action};
$action ||= "Run";
my $loginfo = $0;
$loginfo .= ' ' . $info if $info;
logaction( 'SCRIPTS', $action, $$, $loginfo ) if C4::Context->preference('ScriptLog');
}
1;
__END__

View File

@ -34,6 +34,7 @@ use Koha::SimpleMARC qw(
);
use Koha::MoreUtils;
use Koha::DateUtils qw( dt_from_string );
use JSON qw( encode_json decode_json );
use vars qw(@ISA @EXPORT);
@ -54,6 +55,9 @@ BEGIN {
ModifyRecordsWithTemplate
ModifyRecordWithTemplate
ExportModificationTemplates
ImportModificationTemplates
);
}
@ -636,7 +640,7 @@ sub ModifyRecordWithTemplate {
{
record => $record,
field => $from_field,
subfield => $from_subfield,
subfield => $from_field < 10 ? undef : $from_subfield,
}
);
}
@ -735,6 +739,192 @@ sub ModifyRecordWithTemplate {
return;
}
=head2
ExportModificationTemplates
my $json = ExportModificationTemplates( $template_ids );
Exports MARC modification templates to JSON format.
If $template_ids is provided, only export those templates.
If not provided, export all templates.
Returns a JSON string containing the exported templates.
=cut
sub ExportModificationTemplates {
my ($template_ids) = @_;
my $dbh = C4::Context->dbh;
my @templates;
if ( $template_ids && @$template_ids ) {
my $placeholders = join( ',', ('?') x @$template_ids );
my $sth = $dbh->prepare(
"SELECT * FROM marc_modification_templates WHERE template_id IN ($placeholders) ORDER BY name");
$sth->execute(@$template_ids);
while ( my $template = $sth->fetchrow_hashref() ) {
push( @templates, $template );
}
} else {
my $sth = $dbh->prepare("SELECT * FROM marc_modification_templates ORDER BY name");
$sth->execute();
while ( my $template = $sth->fetchrow_hashref() ) {
push( @templates, $template );
}
}
my $export_data = {
version => 1,
templates => [],
};
foreach my $template (@templates) {
my $template_id = $template->{'template_id'};
my @actions = GetModificationTemplateActions($template_id);
my $template_export = {
template_id => $template_id,
name => $template->{'name'},
actions => [],
};
foreach my $action (@actions) {
my $action_export = {
mmta_id => $action->{'mmta_id'},
ordering => $action->{'ordering'},
action => $action->{'action'},
field_number => $action->{'field_number'},
from_field => $action->{'from_field'},
from_subfield => $action->{'from_subfield'},
field_value => $action->{'field_value'},
to_field => $action->{'to_field'},
to_subfield => $action->{'to_subfield'},
to_regex_search => $action->{'to_regex_search'},
to_regex_replace => $action->{'to_regex_replace'},
to_regex_modifiers => $action->{'to_regex_modifiers'},
conditional => $action->{'conditional'},
conditional_field => $action->{'conditional_field'},
conditional_subfield => $action->{'conditional_subfield'},
conditional_comparison => $action->{'conditional_comparison'},
conditional_value => $action->{'conditional_value'},
conditional_regex => $action->{'conditional_regex'},
description => $action->{'description'},
};
push( @{ $template_export->{actions} }, $action_export );
}
push( @{ $export_data->{templates} }, $template_export );
}
return encode_json($export_data);
}
=head2
ImportModificationTemplates
my $result = ImportModificationTemplates( $json, $skip_existing );
Imports MARC modification templates from JSON format.
$json is the JSON string to import.
$skip_existing: if true, skip templates that already exist (default: false)
Returns a hashref with:
- success: number of successfully imported templates
- skipped: number of skipped templates
- errors: arrayref of error messages
=cut
sub ImportModificationTemplates {
my ( $json, $skip_existing ) = @_;
my $result = {
success => 0,
skipped => 0,
overwrote => 0,
errors => [],
};
my $data;
eval { $data = decode_json($json); };
if ($@) {
push( @{ $result->{errors} }, "Failed to parse JSON: $@" );
return $result;
}
my $version = $data->{version} || 0;
if ( $version != 1 ) {
push( @{ $result->{errors} }, "Unsupported export version: $version" );
return $result;
}
my $templates = $data->{templates} || [];
my $dbh = C4::Context->dbh;
foreach my $template_data (@$templates) {
my $template_name = $template_data->{name};
my $actions = $template_data->{actions} || [];
# Check if template already exists
my $sth = $dbh->prepare("SELECT template_id FROM marc_modification_templates WHERE name = ?");
$sth->execute($template_name);
my $existing = $sth->fetchrow_hashref();
if ( $existing && $skip_existing ) {
$result->{skipped}++;
next;
}
my $template_id;
if ($existing) {
$template_id = $existing->{template_id};
# Delete existing actions for this template
$sth = $dbh->prepare("DELETE FROM marc_modification_template_actions WHERE template_id = ?");
$sth->execute($template_id);
$result->{overwrote}++;
} else {
# Create new template
$sth = $dbh->prepare("INSERT INTO marc_modification_templates (name) VALUES (?)");
$sth->execute($template_name);
$sth = $dbh->prepare("SELECT template_id FROM marc_modification_templates WHERE name = ?");
$sth->execute($template_name);
my $row = $sth->fetchrow_hashref();
$template_id = $row->{template_id};
}
# Add new actions
foreach my $action_data (@$actions) {
AddModificationTemplateAction(
$template_id,
$action_data->{action},
$action_data->{field_number},
$action_data->{from_field},
$action_data->{from_subfield},
$action_data->{field_value},
$action_data->{to_field},
$action_data->{to_subfield},
$action_data->{to_regex_search},
$action_data->{to_regex_replace},
$action_data->{to_regex_modifiers},
$action_data->{conditional},
$action_data->{conditional_field},
$action_data->{conditional_subfield},
$action_data->{conditional_comparison},
$action_data->{conditional_value},
$action_data->{conditional_regex},
$action_data->{description},
);
}
$result->{success}++;
}
return $result;
}
1;
__END__

View File

@ -204,6 +204,15 @@ sub fetch {
return $self;
}
=head3 _fetch_matchpoint
my $matchpoint = $self->_fetch_matchpoint($matchpoint_id);
Loads a matchpoint and its components from the database and returns a
hashref with C<index>, C<score>, and C<components>.
=cut
sub _fetch_matchpoint {
my $self = shift;
my $matchpoint_id = shift;
@ -271,6 +280,15 @@ sub store {
return $self->{'id'};
}
=head3 _del_matcher_components
$self->_del_matcher_components();
Deletes all matchpoint component and norm rows for the current matcher
from the database (used before re-saving).
=cut
sub _del_matcher_components {
my $self = shift;
@ -285,6 +303,14 @@ sub _del_matcher_components {
# matchpoint_component_norms
}
=head3 _update_marc_matchers
$self->_update_marc_matchers();
Updates the C<marc_matchers> row for the current matcher object.
=cut
sub _update_marc_matchers {
my $self = shift;
@ -303,6 +329,14 @@ sub _update_marc_matchers {
);
}
=head3 _new_marc_matchers
$self->_new_marc_matchers();
Inserts a new row into C<marc_matchers> and sets C<$self-E<gt>{'id'}>.
=cut
sub _new_marc_matchers {
my $self = shift;
@ -316,6 +350,14 @@ sub _new_marc_matchers {
$self->{'id'} = $dbh->{'mysql_insertid'};
}
=head3 _store_matcher_components
$self->_store_matcher_components($matchpoint_id, \@components);
Persists the component and norm rows for a matchpoint to the database.
=cut
sub _store_matcher_components {
my $self = shift;
@ -343,6 +385,14 @@ sub _store_matcher_components {
}
=head3 _store_matchpoint
my $matchpoint_id = $self->_store_matchpoint($matchpoint);
Inserts a matchpoint record into the database and returns its new ID.
=cut
sub _store_matchpoint {
my $self = shift;
my $matchpoint = shift;
@ -722,6 +772,7 @@ sub get_matches {
? $target_record->field($biblionumber_tag)->subfield($biblionumber_subfield)
: $target_record->field($biblionumber_tag)->data();
$matches->{$id}->{score} += $matchpoint->{score};
$matches->{$id}->{composite_scores}{ $matchpoint->{'index'} } += $matchpoint->{'score'};
$matches->{$id}->{record} = $target_record;
}
}
@ -752,9 +803,19 @@ sub get_matches {
my ( $authresults, $total ) = $searcher->search_auth_compat( $search_query, 0, 20 );
foreach my $result (@$authresults) {
my $id = $result->{authid};
my $target_record = Koha::Authorities->find($id)->record;
my $id = $result->{authid};
my $target_auth = Koha::Authorities->find($id);
next unless $target_auth;
my $target_record = eval { $target_auth->record };
my $invalid_record = $@ || !$target_record;
if ($invalid_record) {
warn
"Error parsing matched authority record $id, attempting to clean record, record will be skipped if it fails";
$target_record = eval { $target_auth->record_strip_nonxml };
}
next unless $target_record;
$matches->{$id}->{score} += $matchpoint->{'score'};
$matches->{$id}->{composite_scores}{ $matchpoint->{'index'} } += $matchpoint->{'score'};
$matches->{$id}->{record} = $target_record;
}
}
@ -779,8 +840,9 @@ sub get_matches {
foreach my $id ( keys %$matches ) {
push @results, {
record_id => $id,
score => $matches->{$id}->{score}
record_id => $id,
score => $matches->{$id}->{score},
composite_scores => $matches->{$id}->{composite_scores},
};
}
} elsif ( $self->{'record_type'} eq 'authority' ) {
@ -797,8 +859,9 @@ sub get_matches {
foreach my $id ( keys %$matches ) {
push @results, {
record_id => $id,
score => $matches->{$id}->{score}
record_id => $id,
score => $matches->{$id}->{score},
composite_scores => $matches->{$id}->{composite_scores},
};
}
}
@ -841,6 +904,15 @@ sub dump {
return $result;
}
=head3 _passes_required_checks
my $ok = _passes_required_checks($source_record, $target_record, \@matchchecks);
Returns true if the candidate C<$target_record> passes all required field
equality checks against C<$source_record>.
=cut
sub _passes_required_checks {
my ( $source_record, $target_record, $matchchecks ) = @_;
@ -855,6 +927,15 @@ sub _passes_required_checks {
return 1;
}
=head3 _get_match_keys
my @keys = _get_match_keys($source_record, $matchpoint);
Extracts and normalises the set of match key strings from C<$source_record>
for the given C<$matchpoint> definition.
=cut
sub _get_match_keys {
my $source_record = shift;
@ -945,6 +1026,15 @@ sub _get_match_keys {
return @keys;
}
=head3 _parse_match_component
my $component = _parse_match_component($input_component);
Parses a matchpoint component definition hashref and returns a normalised
component structure including tag, subfields, offset, length, and norms.
=cut
sub _parse_match_component {
my $input_component = shift;

View File

@ -94,10 +94,6 @@ The following will be set where applicable:
$flags->{ ODUES }->{itemlist} ref-to-array: list of overdue books
$flags->{ ODUES }->{itemlisttext} Text list of overdue items -- deprecated
$flags->{WAITING} Set if any of patron's reserves are available
$flags->{WAITING}->{message} Message -- deprecated
$flags->{WAITING}->{itemlist} ref-to-array: list of available items
=over
=item C<$flags-E<gt>{ODUES}-E<gt>{itemlist}> is a reference-to-array listing the
@ -108,10 +104,6 @@ biblioitems, and items tables of the Koha database.
=item C<$flags-E<gt>{ODUES}-E<gt>{itemlisttext}> is a string giving a text listing of
the overdue items, one per line. Deprecated.
=item C<$flags-E<gt>{WAITING}-E<gt>{itemlist}> is a reference-to-array listing the
available items. Each element is a reference-to-hash whose keys are
fields from the reserves table of the Koha database.
=back
All the "message" fields that include language generated in this function are deprecated,
@ -203,14 +195,6 @@ sub patronflags {
$flags{'ODUES'} = \%flaginfo;
}
my $waiting_holds = $patron->holds->search( { found => 'W' } );
my $nowaiting = $waiting_holds->count;
if ( $nowaiting > 0 ) {
my %flaginfo;
$flaginfo{'message'} = "Reserved items available";
$flaginfo{'itemlist'} = $waiting_holds->unblessed;
$flags{'WAITING'} = \%flaginfo;
}
return ( \%flags );
}

View File

@ -140,8 +140,8 @@ sub SetMessagingPreference {
return;
}
}
$params->{'days_in_advance'} = undef unless exists( $params->{'days_in_advance'} );
$params->{'wants_digest'} = 0 unless exists( $params->{'wants_digest'} );
$params->{'days_in_advance'} = undef if !exists $params->{'days_in_advance'} || $params->{'days_in_advance'} <= 0;
$params->{'wants_digest'} = 0 unless exists( $params->{'wants_digest'} );
my $dbh = C4::Context->dbh();
@ -160,7 +160,9 @@ END_SQL
my $sth = $dbh->prepare($delete_sql);
my $deleted = $sth->execute(@bind_params);
if ( $params->{'message_transport_types'} ) {
if ( $params->{'message_transport_types'}
&& !( $params->{'message_attribute_id'} == 2 && !$params->{'days_in_advance'} ) )
{
my $insert_bmp = <<'END_SQL';
INSERT INTO borrower_message_preferences
(borrower_message_preference_id, borrowernumber, categorycode, message_attribute_id, days_in_advance, wants_digest)

View File

@ -45,6 +45,7 @@ use URI::Escape;
use C4::Auth qw( get_template_and_user );
use C4::Context;
use C4::Templates;
use Koha::Exceptions;
=head1 NAME
@ -391,12 +392,19 @@ Missing POD for output_error.
=cut
sub output_error {
my ( $query, $error ) = @_;
my ( $query, $error, $params ) = @_;
if ( !$error || $error =~ /\D/ ) {
Koha::Exceptions::WrongParameter->throw('output_error requires $error to be an integer');
}
my $interface = 'intranet';
$interface = 'opac' if ( $params && $params->{interface} eq 'opac' );
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
{
template_name => 'errors/errorpage.tt',
query => $query,
type => 'intranet',
type => $interface,
authnotrequired => 1,
}
);
@ -405,7 +413,9 @@ sub output_error {
admin => $admin,
errno => $error,
);
output_with_http_headers $query, $cookie, $template->output, 'html', '404 Not Found';
#NOTE: Technically, it is enough to just include the status code. You don't have to include the reason phrase.
output_with_http_headers $query, $cookie, $template->output, 'html', $error;
}
=item parametrized_url
@ -428,8 +438,7 @@ sub parametrized_url {
=item redirect_if_opac_suppressed
redirect_if_opac_suppressed( $query, $biblio )
if C4::Context->preference('OpacSuppression');
redirect_if_opac_suppressed( $query, $biblio );
For a given I<Koha::Biblio> object, it handles redirection if it is suppressed
from the OPAC.
@ -446,20 +455,9 @@ sub redirect_if_opac_suppressed {
} else {
$redirect_url = "/cgi-bin/koha/errors/404.pl";
}
if ( $biblio->opac_suppressed() ) {
# if OPAC suppression by IP address
if ( C4::Context->preference('OpacSuppressionByIPRange') ) {
my $IPAddress = $ENV{'REMOTE_ADDR'};
my $IPRange = C4::Context->preference('OpacSuppressionByIPRange');
if ( $IPAddress !~ /^$IPRange/ ) {
print $query->redirect($redirect_url);
C4::Auth::safe_exit();
}
} else {
print $query->redirect($redirect_url);
C4::Auth::safe_exit();
}
if ( C4::Context->is_opac_suppressed && $biblio->opac_suppressed() ) {
print $query->redirect($redirect_url);
C4::Auth::safe_exit();
}
}

View File

@ -32,14 +32,11 @@ BEGIN {
GetBranchcodesWithOverdueRules
get_chargeable_units
GetOverduesForBranch
GetOverdueMessageTransportTypes
parse_overdues_letter
GetIssuesIteminfo
);
}
use Date::Calc qw( Today );
use Date::Manip qw( UnixDate );
use List::MoreUtils qw( uniq );
use POSIX qw( ceil floor );
use Locale::Currency::Format 1.28 qw( currency_format FMT_SYMBOL );
@ -346,9 +343,12 @@ sub get_chargeable_units {
my $charge_units = 0;
my $charge_duration;
$unit //= 'days';
if ( $unit eq 'hours' ) {
if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
my $calendar = Koha::Calendar->new( branchcode => $branchcode );
my $calendar = Koha::Library::Calendar->new( branchcode => $branchcode );
$charge_duration = $calendar->hours_between( $date_due, $date_returned );
} else {
$charge_duration = $date_returned->delta_ms($date_due);
@ -359,7 +359,7 @@ sub get_chargeable_units {
return $charge_duration->in_units('hours');
} else { # days
if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
my $calendar = Koha::Calendar->new( branchcode => $branchcode );
my $calendar = Koha::Library::Calendar->new( branchcode => $branchcode );
$charge_duration = $calendar->days_between( $date_due, $date_returned );
} else {
$charge_duration = $date_returned->delta_days($date_due);
@ -368,131 +368,6 @@ sub get_chargeable_units {
}
}
=head2 GetSpecialHolidays
&GetSpecialHolidays($date_dues,$itemnumber);
return number of special days between date of the day and date due
C<$date_dues> is the envisaged date of book return.
C<$itemnumber> is the book's item number.
=cut
sub GetSpecialHolidays {
my ( $date_dues, $itemnumber ) = @_;
# calcul the today date
my $today = join "-", &Today();
# return the holdingbranch
my $iteminfo = GetIssuesIteminfo($itemnumber);
# use sql request to find all date between date_due and today
my $dbh = C4::Context->dbh;
my $query = qq|SELECT DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') as date
FROM `special_holidays`
WHERE DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') >= ?
AND DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') <= ?
AND branchcode=?
|;
my @result = GetWdayFromItemnumber($itemnumber);
my @result_date;
my $wday;
my $dateinsec;
my $sth = $dbh->prepare($query);
$sth->execute( $date_dues, $today, $iteminfo->{'branchcode'} )
; # FIXME: just use NOW() in SQL instead of passing in $today
while ( my $special_date = $sth->fetchrow_hashref ) {
push( @result_date, $special_date );
}
my $specialdaycount = scalar(@result_date);
for ( my $i = 0 ; $i < scalar(@result_date) ; $i++ ) {
$dateinsec = UnixDate( $result_date[$i]->{'date'}, "%o" );
( undef, undef, undef, undef, undef, undef, $wday, undef, undef ) = localtime($dateinsec);
for ( my $j = 0 ; $j < scalar(@result) ; $j++ ) {
if ( $wday == ( $result[$j]->{'weekday'} ) ) {
$specialdaycount--;
}
}
}
return $specialdaycount;
}
=head2 GetRepeatableHolidays
&GetRepeatableHolidays($date_dues, $itemnumber, $difference,);
return number of day closed between date of the day and date due
C<$date_dues> is the envisaged date of book return.
C<$itemnumber> is item number.
C<$difference> numbers of between day date of the day and date due
=cut
sub GetRepeatableHolidays {
my ( $date_dues, $itemnumber, $difference ) = @_;
my $dateinsec = UnixDate( $date_dues, "%o" );
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime($dateinsec);
my @result = GetWdayFromItemnumber($itemnumber);
my @dayclosedcount;
my $j;
for ( my $i = 0 ; $i < scalar(@result) ; $i++ ) {
my $k = $wday;
for ( $j = 0 ; $j < $difference ; $j++ ) {
if ( $result[$i]->{'weekday'} == $k ) {
push( @dayclosedcount, $k );
}
$k++;
( $k = 0 ) if ( $k eq 7 );
}
}
return scalar(@dayclosedcount);
}
=head2 GetWayFromItemnumber
&Getwdayfromitemnumber($itemnumber);
return the different week day from repeatable_holidays table
C<$itemnumber> is item number.
=cut
=head2 GetWdayFromItemnumber
Missing POD for GetWdayFromItemnumber.
=cut
sub GetWdayFromItemnumber {
my ($itemnumber) = @_;
my $iteminfo = GetIssuesIteminfo($itemnumber);
my @result;
my $query = qq|SELECT weekday
FROM repeatable_holidays
WHERE branchcode=?
|;
my $sth = C4::Context->dbh->prepare($query);
$sth->execute( $iteminfo->{'branchcode'} );
while ( my $weekday = $sth->fetchrow_hashref ) {
push( @result, $weekday );
}
return @result;
}
=head2 GetIssuesIteminfo
&GetIssuesIteminfo($itemnumber);
@ -647,7 +522,17 @@ sub UpdateFine {
};
my $desc = $letter ? $letter->{content} : sprintf( "Item %s - due %s", $itemnum, output_pref($due) );
my $account = Koha::Account->new( { patron_id => $borrowernumber } );
my $account = Koha::Account->new( { patron_id => $borrowernumber } );
my $item_obj = Koha::Items->find($itemnum);
my $issue_obj = Koha::Checkouts->find($issue_id);
my $rule_branch = Koha::Checkout->branch_for_fee_context(
fee_type => 'OVERDUE',
patron => $patron,
item => $item_obj,
issue => $issue_obj,
);
$accountline = $account->add_debit(
{
amount => $amount,
@ -655,7 +540,7 @@ sub UpdateFine {
note => undef,
user_id => undef,
interface => C4::Context->interface,
library_id => undef, #FIXME: Should we grab the checkout or circ-control branch here perhaps?
library_id => $rule_branch,
type => 'OVERDUE',
item_id => $itemnum,
issue_id => $issue_id,
@ -707,21 +592,19 @@ returns a list of branch codes for branches with overdue rules defined.
=cut
sub GetBranchcodesWithOverdueRules {
my $dbh = C4::Context->dbh;
my $branchcodes = $dbh->selectcol_arrayref(
q|
SELECT DISTINCT(branchcode)
FROM overduerules
WHERE delay1 IS NOT NULL
ORDER BY branchcode
|
);
if ( defined $branchcodes->[0] && $branchcodes->[0] eq '' ) {
# If a default rule exists, all branches should be returned
return Koha::Libraries->search( {}, { order_by => 'branchname' } )->get_column('branchcode');
my @branchcodes = Koha::CirculationRules->search(
{ rule_name => 'overdue_1_delay' },
{
order_by => 'branchcode',
columns => ['branchcode'],
distinct => 1,
}
)->get_column('branchcode');
if ( @branchcodes && !defined $branchcodes[0] ) {
@branchcodes = Koha::Libraries->search( {}, { order_by => 'branchname' } )->get_column('branchcode');
}
return @$branchcodes;
return @branchcodes;
}
=head2 GetOverduesForBranch
@ -788,39 +671,6 @@ sub GetOverduesForBranch {
}
}
=head2 GetOverdueMessageTransportTypes
my $message_transport_types = GetOverdueMessageTransportTypes( $branchcode, $categorycode, $letternumber);
return a arrayref with all message_transport_type for given branchcode, categorycode and letternumber(1,2 or 3)
=cut
sub GetOverdueMessageTransportTypes {
my ( $branchcode, $categorycode, $letternumber ) = @_;
return unless $categorycode and $letternumber;
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare( "
SELECT message_transport_type
FROM overduerules odr LEFT JOIN overduerules_transport_types ott USING (overduerules_id)
WHERE branchcode = ?
AND categorycode = ?
AND letternumber = ?
" );
$sth->execute( $branchcode, $categorycode, $letternumber );
my @mtts;
while ( my $mtt = $sth->fetchrow ) {
push @mtts, $mtt;
}
# Put 'print' in first if exists
# It avoid to sent a print notice with an email or sms template is no email or sms is defined
@mtts = uniq( 'print', @mtts )
if grep { $_ eq 'print' } @mtts;
return \@mtts;
}
=head2 parse_overdues_letter
parses the letter template, replacing the placeholders with data

View File

@ -919,9 +919,11 @@ sub marc2cites {
$author = $field->subfield("a");
}
if ( $author =~ /([^,]+),?(.*)/ ) {
my $surname = $1;
my $forenames = $2;
my %a;
( $a{'surname'} = $1 ) =~ s/$re_clean//g;
$a{'forenames'} = [ map { my $t = $_; $t =~ s/$re_clean//g; $t } split ' ', $2 ];
( $a{'surname'} = $surname ) =~ s/$re_clean//g;
$a{'forenames'} = [ map { my $t = $_; $t =~ s/$re_clean//g; $t } split ' ', $forenames ];
push( @authors, \%a );
}
}
@ -931,6 +933,7 @@ sub marc2cites {
if ( $marcflavour eq "UNIMARC" ) {
%publication = (
title => $record->subfield( "200", "a" ) || "",
subtitle => $record->subfield( "200", "e" ) || "",
place => $record->subfield( "210", "a" ) || "",
publisher => $record->subfield( "210", "c" ) || "",
date => $record->subfield( "210", "d" ) || $record->subfield( "210", "h" ) || ""
@ -938,6 +941,7 @@ sub marc2cites {
} else {
%publication = (
title => $record->subfield( "245", "a" ) || "",
subtitle => $record->subfield( "245", "b" ) || "",
place => $record->subfield( "264", "a" ) || $record->subfield( "260", "a" ) || "",
publisher => $record->subfield( "264", "b" ) || $record->subfield( "260", "b" ) || "",
date => $record->subfield( "264", "c" )
@ -978,13 +982,16 @@ sub marc2cites {
if ( $publication{date} ) {
$cites{'Harvard'} .= ' (' . $publication{'date'} . '). ';
$cites{'Chicago'} .= ' ' . $publication{'date'} . '. ';
$cites{'MLA'} .= ' ' . $publication{'title'} . '. ';
$cites{'APA'} .= ' (' . $publication{'date'} . '). ';
$cites{'MLA'} .=
' ' . $publication{'title'} . ( $publication{'subtitle'} ? ': ' . $publication{'subtitle'} : '' ) . '. ';
$cites{'APA'} .= ' (' . $publication{'date'} . '). ';
}
$cites{'Harvard'} .= $publication{'title'} . '. ';
$cites{'Chicago'} .= $publication{'title'} . '. ';
$cites{'MLA'} .= $publication{'place'} . ': ';
$cites{'APA'} .= $publication{'title'} . '. ';
$cites{'Harvard'} .=
$publication{'title'} . ( $publication{'subtitle'} ? ': ' . $publication{'subtitle'} : '' ) . '. ';
$cites{'Chicago'} .=
$publication{'title'} . ( $publication{'subtitle'} ? ': ' . $publication{'subtitle'} : '' ) . '. ';
$cites{'MLA'} .= $publication{'place'} . ': ';
$cites{'APA'} .= $publication{'title'} . ( $publication{'subtitle'} ? ': ' . $publication{'subtitle'} : '' ) . '. ';
$cites{'Harvard'} .= $publication{'place'} . ': ';
$cites{'Chicago'} .= $publication{'place'} . ': ';
$cites{'MLA'} .= $publication{'publisher'} . '. ';

View File

@ -49,18 +49,10 @@ This will return a list of all the available delimiters.
=cut
sub GetDelimiterChoices {
my $dbh = C4::Context->dbh;
my $default = C4::Context->preference('CSVDelimiter');
my $sth = $dbh->prepare( "
SELECT options, value
FROM systempreferences
WHERE variable = 'CSVDelimiter'
" );
$sth->execute();
my ( $choices, $default ) = $sth->fetchrow;
my @dels = split /\|/, $choices;
# Use the centralized list of allowed delimiters from C4::Context
my @dels = C4::Context::ALLOWED_CSV_DELIMITERS;
return {
values => \@dels,

View File

@ -54,9 +54,11 @@ use Koha::Logger;
use Koha::Notice::Templates;
use Koha::Patron::Categories;
use Koha::Patrons;
use Koha::Report;
use Koha::Reports;
use Koha::SharedContent;
use Koha::TemplateUtils qw( process_tt );
use C4::Letters qw( GetPreparedLetter );
=head1 NAME
@ -604,6 +606,7 @@ sub execute_query {
my ( $is_sql_valid, $errors ) = Koha::Report->new( { savedsql => $sql } )->is_sql_valid;
return ( undef, @{$errors}[0] ) unless $is_sql_valid;
$sql = Koha::Report->apply_execution_time_limit($sql);
foreach my $sql_param (@$sql_params) {
if ( $sql_param =~ m/\n/ ) {
@ -878,7 +881,54 @@ sub get_saved_reports {
my $result = $dbh->selectall_arrayref( $query, { Slice => {} }, @args );
return $result;
my $limit_pref_enabled = C4::Context->preference('LimitReportsByLibrary');
return $result unless $limit_pref_enabled;
# Preference enabled AND showing all: attach library limits metadata, but do not filter
if ( $filter->{show_all_reports} ) {
foreach my $report (@$result) {
my $report_obj = Koha::Reports->find( $report->{id} );
if ($report_obj) {
my $limits_rs = $report_obj->get_library_limits;
if ($limits_rs) {
my @branches =
map { { branchcode => $_->branchcode, branchname => $_->branchname } } $limits_rs->as_list;
$report->{library_limits} = \@branches;
} else {
$report->{library_limits} = [];
}
} else {
$report->{library_limits} = [];
}
}
return $result;
}
# Preference enabled AND not showing all: apply library limits filtering
my $limited_reports = Koha::Reports->search_with_library_limits( {}, {}, C4::Context::mybranch() );
my %allowed = map { $_->{id} => 1 } @{ $limited_reports->unblessed };
my @limited = grep { $allowed{ $_->{id} } } @$result;
# Attach library limits data for the filtered list
foreach my $report (@limited) {
my $report_obj = Koha::Reports->find( $report->{id} );
if ($report_obj) {
my $limits_rs = $report_obj->get_library_limits;
if ($limits_rs) {
my @branches =
map { { branchcode => $_->branchcode, branchname => $_->branchname } } $limits_rs->as_list;
$report->{library_limits} = \@branches;
} else {
$report->{library_limits} = [];
}
} else {
$report->{library_limits} = [];
}
}
return \@limited;
}
=head2 get_column_type($column)
@ -1168,8 +1218,7 @@ sub EmailReport {
my ( $sth, $errors ) = execute_query( { sql => $sql, report_id => $report_id } );
return ( undef, [ { FATAL => "REPORT_FAIL" } ] ) if $errors;
my $counter = 1;
my $template = $letter->{content};
my $counter = 1;
while ( my $row = $sth->fetchrow_hashref() ) {
my $email;
@ -1182,13 +1231,16 @@ sub EmailReport {
my $from_address = $from || $row->{from};
my $to_address = $row->{$email_col};
push( @errors, { NOT_PARSE => $counter } ) unless my $content = process_tt( $template, $row );
my $letter = {%$letter}; # GetPreparedLetter mutates, so make a local copy beforehand
push( @errors, { NOT_PARSE => $counter } ) unless my $rendered = GetPreparedLetter(
letter => $letter,
objects => $row
);
$counter++;
next if scalar @errors > $err_count; #If any problems, try next
next if scalar @errors > $err_count; #If any problems, try next
$letter->{content} = $content;
$email->{borrowernumber} = $row->{borrowernumber};
$email->{letter} = {%$letter};
$email->{letter} = $rendered;
$email->{from_address} = $from_address;
$email->{to_address} = $to_address;

View File

@ -1,7 +1,7 @@
package C4::Reserves;
# Copyright 2000-2002 Katipo Communications
# 2006 SAN Ouest Provence
# 2006 SAN Ouest Provence # codespell:ignore
# 2007-2010 BibLibre Paul POULAIN
# 2011 Catalyst IT
#
@ -29,9 +29,6 @@ BEGIN {
GetReserveStatus
ChargeReserveFee
GetReserveFee
ModReserveAffect
ModReserve
ModReserveStatus
@ -60,6 +57,8 @@ BEGIN {
GetMaxPatronHoldsForRecord
MergeHolds
FixPriority
);
}
@ -67,7 +66,8 @@ use JSON qw( to_json );
use C4::Accounts;
use C4::Biblio qw( GetMarcFromKohaField );
use C4::Circulation qw( CheckIfIssuedToPatron GetAgeRestriction GetBranchItemRule );
use C4::Circulation qw( CheckIfIssuedToPatron GetBranchItemRule );
use Koha::Policy::Biblio::AgeRestriction;
use C4::Context;
use C4::Items qw( CartToShelf get_hostitemnumbers_of );
use C4::Letters;
@ -77,7 +77,7 @@ use C4::Members;
use Koha::Account::Lines;
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
use Koha::Biblios;
use Koha::Calendar;
use Koha::Library::Calendar;
use Koha::Cache::Memory::Lite;
use Koha::CirculationRules;
use Koha::Database;
@ -111,10 +111,10 @@ This modules provides some functions to deal with reservations.
- priority >0 : then the reserve is at 1st stage, and not yet affected to any item.
=0 : then the reserve is being dealt
- found : NULL : means the patron requested the 1st available, and we haven't chosen the item
T(ransit) : the reserve is linked to an item but is in transit to the pickup branch
W(aiting) : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
F(inished) : the reserve has been completed, and is done
P(rocessing) : reserved item has been returned using self-check machine and reserve needs to be confirmed
T (Transit) : the reserve is linked to an item but is in transit to the pickup branch
W (Waiting) : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
F (Finished) : the reserve has been completed, and is done
P (Processing) : reserved item has been returned using self-check machine and reserve needs to be confirmed
by librarian before notice is send and status changed to waiting.
Applicable only if HoldsNeedProcessingSIP system preference is set.
- itemnumber : empty : the reserve is still unaffected to an item
@ -144,18 +144,20 @@ This modules provides some functions to deal with reservations.
AddReserve(
{
branchcode => $branchcode,
borrowernumber => $borrowernumber,
biblionumber => $biblionumber,
priority => $priority,
reservation_date => $reservation_date,
expiration_date => $expiration_date,
notes => $notes,
title => $title,
itemnumber => $itemnumber,
found => $found,
itemtype => $itemtype,
item_group_id => $item_group_id
branchcode => $branchcode,
borrowernumber => $borrowernumber,
biblionumber => $biblionumber,
priority => $priority,
reservation_date => $reservation_date,
expiration_date => $expiration_date,
notes => $notes,
title => $title,
itemnumber => $itemnumber,
found => $found,
itemtype => $itemtype,
item_group_id => $item_group_id,
supplyill => $iso18626_request_id;
iso18626_payload => $payload_hash;
}
);
@ -194,6 +196,8 @@ sub AddReserve {
my $item_group_id = $params->{item_group_id};
my $confirmations = $params->{confirmations};
my $forced = $params->{forced};
my $supplyill = $params->{supplyill};
my $iso18626_payload = $params->{iso18626_payload};
$resdate ||= dt_from_string;
@ -258,6 +262,9 @@ sub AddReserve {
non_priority => $non_priority ? 1 : 0,
}
)->store();
$hold->iso18626_attach_hold_and_biblio($supplyill);
$hold->set_waiting() if $found && $found eq 'W';
# record patron activity
@ -265,32 +272,39 @@ sub AddReserve {
# Log the hold creation
if ( C4::Context->preference('HoldsLog') ) {
my $info = $hold->id;
if ( defined($confirmations) || defined($forced) ) {
$info = to_json(
{
hold => $hold->id,
branchcode => $hold->branchcode,
biblionumber => $hold->biblionumber,
itemnumber => $hold->itemnumber,
confirmations => $confirmations,
forced => $forced
},
{ pretty => 1, canonical => 1 }
);
}
my $info = to_json(
{
hold => $hold->id,
branchcode => $hold->branchcode,
biblionumber => $hold->biblionumber,
borrowernumber => $hold->borrowernumber,
itemnumber => $hold->itemnumber,
desk_id => $hold->desk_id,
hold_group_id => $hold->hold_group_id,
item_group_id => $hold->item_group_id,
item_level_hold => $hold->item_level_hold,
itemtype => $hold->itemtype,
lowestPriority => $hold->lowestPriority,
non_priority => $hold->non_priority,
patron_expiration_date => $hold->patron_expiration_date,
priority => $hold->priority,
reservenotes => $hold->reservenotes,
confirmations => $confirmations || [],
forced => $forced || []
},
{ pretty => 1, canonical => 1 }
);
logaction( 'HOLDS', 'CREATE', $hold->id, $info );
}
my $reserve_id = $hold->id();
# add a reserve fee if needed
if ( C4::Context->preference('HoldFeeMode') ne 'any_time_is_collected' ) {
my $reserve_fee = GetReserveFee( $borrowernumber, $biblionumber );
ChargeReserveFee( $borrowernumber, $reserve_fee, $title );
if ( $hold->should_charge('placement') ) {
$hold->charge_hold_fee();
}
_FixPriority( { biblionumber => $biblionumber } );
FixPriority( { biblionumber => $biblionumber } );
# Send e-mail to librarian if syspref is active
if ( C4::Context->preference("emailLibrarianWhenHoldIsPlaced") ) {
@ -354,6 +368,8 @@ sub AddReserve {
}
}
$hold->progress_iso18626_request( 'hold_created', $iso18626_payload );
Koha::Plugins->call( 'after_hold_create', $hold );
Koha::Plugins->call(
'after_hold_action',
@ -428,7 +444,7 @@ sub CanBookBeReserved {
{
-or => [
biblionumber => $biblionumber,
itemnumber => { -in => @hostitemnumbers }
itemnumber => { -in => \@hostitemnumbers }
]
}
);
@ -471,6 +487,16 @@ sub CanBookBeReserved {
our $CanItemBeReserved_cache_key;
=head2 _cache
_cache($return);
Internal helper that stores C<$return> in the memory cache under
C<$CanItemBeReserved_cache_key> and returns it. Used by C<CanItemBeReserved>
to avoid redundant lookups within the same request.
=cut
sub _cache {
my ($return) = @_;
my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
@ -512,9 +538,9 @@ sub CanItemBeReserved {
my $biblio = $item->biblio;
# Check for the age restriction
my $ageRestriction = C4::Circulation::GetAgeRestriction( $biblio->biblioitem->agerestriction );
my $age_check = Koha::Policy::Biblio::AgeRestriction->check( $biblio, $patron );
return _cache { status => 'ageRestricted' }
if $ageRestriction && $patron->dateofbirth && $ageRestriction > $patron->get_age();
if !$age_check;
}
# Check that the patron doesn't have an item level hold on this item already
@ -609,9 +635,10 @@ sub CanItemBeReserved {
}
if ( !$params->{ignore_hold_counts} ) {
# we retrieve count
# we retrieve count, treating each hold group as a single unit
my $querycount = q{
SELECT count(*) AS count
SELECT COUNT( CASE WHEN reserves . hold_group_id IS NULL THEN 1 END ) +
COUNT( DISTINCT reserves.hold_group_id ) AS count
FROM reserves
LEFT JOIN items USING (itemnumber)
LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
@ -663,7 +690,7 @@ sub CanItemBeReserved {
}
);
if ( !$params->{ignore_hold_counts} && $rule && defined( $rule->rule_value ) && $rule->rule_value ne '' ) {
my $total_holds_count = Koha::Holds->search( { borrowernumber => $patron->borrowernumber } )->count();
my $total_holds_count = Koha::Holds->count_holds( { borrowernumber => $patron->borrowernumber } );
return _cache { status => 'tooManyReserves', limit => $rule->rule_value }
if $total_holds_count >= $rule->rule_value;
@ -722,84 +749,6 @@ sub CanItemBeReserved {
return _cache { status => 'OK' };
}
=head2 ChargeReserveFee
$fee = ChargeReserveFee( $borrowernumber, $fee, $title );
Charge the fee for a reserve (if $fee > 0)
=cut
sub ChargeReserveFee {
my ( $borrowernumber, $fee, $title ) = @_;
return if !$fee || $fee == 0; # the last test is needed to include 0.00
Koha::Account->new( { patron_id => $borrowernumber } )->add_debit(
{
amount => $fee,
description => $title,
note => undef,
user_id => C4::Context->userenv ? C4::Context->userenv->{'number'} : undef,
library_id => C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef,
interface => C4::Context->interface,
invoice_type => undef,
type => 'RESERVE',
item_id => undef
}
);
}
=head2 GetReserveFee
$fee = GetReserveFee( $borrowernumber, $biblionumber );
Calculate the fee for a reserve (if applicable).
=cut
sub GetReserveFee {
my ( $borrowernumber, $biblionumber ) = @_;
my $borquery = qq{
SELECT reservefee FROM borrowers LEFT JOIN categories ON borrowers.categorycode = categories.categorycode WHERE borrowernumber = ?
};
my $issue_qry = qq{
SELECT COUNT(*) FROM items
LEFT JOIN issues USING (itemnumber)
WHERE items.biblionumber=? AND issues.issue_id IS NULL
};
my $holds_qry = qq{
SELECT COUNT(*) FROM reserves WHERE biblionumber=? AND borrowernumber<>?
};
my $dbh = C4::Context->dbh;
my ($fee) = $dbh->selectrow_array( $borquery, undef, ($borrowernumber) );
$fee += 0;
my $hold_fee_mode = C4::Context->preference('HoldFeeMode') || 'not_always';
if ( $fee and $fee > 0 and $hold_fee_mode eq 'not_always' ) {
# This is a reconstruction of the old code:
# Compare number of items with items issued, and optionally check holds
# If not all items are issued and there are no holds: charge no fee
# NOTE: Lost, damaged, not-for-loan, etc. are just ignored here
my ( $notissued, $reserved );
($notissued) = $dbh->selectrow_array(
$issue_qry, undef,
($biblionumber)
);
if ( $notissued == 0 ) {
# all items are issued
($reserved) = $dbh->selectrow_array(
$holds_qry, undef,
( $biblionumber, $borrowernumber )
);
$fee = 0 if $reserved == 0;
} else {
$fee = 0;
}
}
return $fee;
}
=head2 GetReserveStatus
$reservestatus = GetReserveStatus($itemnumber);
@ -947,6 +896,54 @@ sub CheckReserves {
}
}
# Local holds exclusivity period: when the queue has
# targeted a local-group item for this hold and we're still
# within the exclusivity window, skip non-local items so the
# targeted local item can fulfil it. If the queue has not yet
# run for this reserve (no hold_fill_targets row), we fall
# through and let the hold be filled normally.
# This is independent of LocalHoldsPriority.
my $excl_period = C4::Context->preference('LocalHoldsExclusivityPeriod');
if ( $excl_period && $res->{reserve_id} ) {
my $excl_item_control =
C4::Context->preference('LocalHoldsExclusivityItemControl') || 'holdingbranch';
my $excl_patron_control =
C4::Context->preference('LocalHoldsExclusivityPatronControl') || 'PickupLibrary';
my $excl_item_branchcode = $item->$excl_item_control;
$patron //= Koha::Patrons->find( $res->{borrowernumber} );
my $excl_patron_branchcode =
( $excl_patron_control eq 'PickupLibrary' ) ? $res->{branchcode}
: ( $excl_patron_control eq 'HomeLibrary' ) ? $patron->branchcode
: undef;
my $is_local_for_exclusivity = 0;
if ( $excl_item_branchcode && $excl_patron_branchcode ) {
if ( $excl_item_branchcode eq $excl_patron_branchcode ) {
$is_local_for_exclusivity = 1;
} else {
my $excl_library = Koha::Libraries->find( { branchcode => $excl_item_branchcode } );
$is_local_for_exclusivity = 1
if $excl_library
&& $excl_library->validate_hold_sibling( { branchcode => $excl_patron_branchcode } );
}
}
if ( !$is_local_for_exclusivity ) {
my $reservedate_dt = eval { dt_from_string( $res->{reservedate} ) };
if ($reservedate_dt) {
my $age_days = dt_from_string->delta_days($reservedate_dt)->in_units('days');
if ( $age_days < $excl_period ) {
my $target =
Koha::Database->new->schema->resultset('HoldFillTarget')
->search( { reserve_id => $res->{reserve_id} }, { rows => 1 } )
->next;
next if $target && $target->local_holdgroup_match;
}
}
}
}
# See if this item is more important than what we've got so far
if ( ( $res->{'priority'} && $res->{'priority'} < $priority )
|| $local_hold_match
@ -1092,7 +1089,7 @@ sub CancelExpiredReserves {
my $cache_key = sprintf "Calendar_CancelExpiredReserves:%s", $hold->branchcode;
my $calendar = $cache->get_from_cache($cache_key);
if ( !$calendar ) {
$calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
$calendar = Koha::Library::Calendar->new( branchcode => $hold->branchcode );
$cache->set_in_cache( $cache_key, $calendar );
}
@ -1186,6 +1183,8 @@ for a bib.
If C<$rank> is 'del', the hold request is cancelled.
If C<$rank> is 'recall', the hold request is converted to a recall request.
If C<$rank> is an integer greater than zero, the priority of
the request is set to that value. Since priority != 0 means
that the item is not waiting on the hold shelf, setting the
@ -1219,6 +1218,7 @@ sub ModReserve {
my $biblionumber = $params->{'biblionumber'};
my $cancellation_reason = $params->{'cancellation_reason'};
my $date = $params->{expirationdate};
my $skip_fixup_priority = $params->{skip_fixup_priority};
return if defined $rank && $rank eq "n";
@ -1242,6 +1242,43 @@ sub ModReserve {
if ( $rank eq "del" ) {
$hold->cancel( { cancellation_reason => $cancellation_reason } );
} elsif ( $rank eq "recall" ) {
if ( !$biblionumber ) {
$biblionumber = $hold->biblionumber;
}
my $biblio = Koha::Biblios->find($biblionumber);
if ( !$borrowernumber ) {
$borrowernumber = $hold->borrowernumber;
}
my $patron = Koha::Patrons->find($borrowernumber);
if ( $hold->item_level_hold ) {
if ( $hold->item->can_be_recalled( { patron => $patron, hold_convert => 1 } ) ) {
Koha::Recalls->add_recall(
{
patron => $patron,
biblio => $biblio,
branchcode => $hold->branchcode,
expirationdate => $date,
interface => 'intranet',
item => $hold->item,
}
);
$hold->cancel( { cancellation_reason => $cancellation_reason } );
}
} else {
if ( $biblio->can_be_recalled( { patron => $patron, hold_convert => 1 } ) ) {
Koha::Recalls->add_recall(
{
patron => $patron,
biblio => $biblio,
branchcode => $hold->branchcode,
expirationdate => $date,
interface => 'intranet',
}
);
$hold->cancel( { cancellation_reason => $cancellation_reason } );
}
}
} elsif ( $hold->found && $hold->priority eq '0' && $date ) {
# The only column that can be updated for a found hold is the expiration date
@ -1263,6 +1300,9 @@ sub ModReserve {
if ( exists $params->{expirationdate} ) {
$properties->{expirationdate} = $params->{expirationdate} || undef;
}
if ( exists $params->{item_level_hold} ) {
$properties->{item_level_hold} = $params->{item_level_hold};
}
$hold->set($properties)->store();
@ -1277,7 +1317,7 @@ sub ModReserve {
}
}
_FixPriority( { reserve_id => $reserve_id, rank => $rank } );
FixPriority( { reserve_id => $reserve_id, rank => $rank } ) unless $skip_fixup_priority;
logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, $hold, undef, $original )
if C4::Context->preference('HoldsLog');
@ -1390,7 +1430,7 @@ sub ModReserveAffect {
_koha_notify_hold_changed($hold) if $notify_library;
_FixPriority( { biblionumber => $biblionumber } );
FixPriority( { biblionumber => $biblionumber } );
my $item = Koha::Items->find($itemnumber);
if ( $item->location
&& $item->location eq 'CART'
@ -1477,7 +1517,7 @@ sub ModReserveMinusPriority {
$sth_upd->execute( $itemnumber, $reserve_id );
# second step update all others reserves
_FixPriority( { reserve_id => $reserve_id, rank => '0' } );
FixPriority( { reserve_id => $reserve_id, rank => '0' } );
}
=head2 IsAvailableForItemLevelRequest
@ -1548,9 +1588,9 @@ sub IsAvailableForItemLevelRequest {
my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } );
if ( $on_shelf_holds == 1 ) {
if ( $on_shelf_holds == 1 ) { # Allow
return 1;
} elsif ( $on_shelf_holds == 2 ) {
} elsif ( $on_shelf_holds == 2 ) { # If all unavailable
# These calculations work at the biblio level, and can be expensive
# we use the in-memory cache to avoid calling once per item when looping items on a biblio
@ -1566,7 +1606,7 @@ sub IsAvailableForItemLevelRequest {
$memory_cache->set_in_cache( $cache_key, $any_available );
return $any_available ? 0 : 1;
} else { # on_shelf_holds == 0 "If any unavailable" (the description is rather cryptic and could still be improved)
} else { # on_shelf_holds == 0 [If any unavailable]
return $item->notforloan < 0 || $item->onloan || $item->holds->filter_by_found->count;
}
}
@ -1634,14 +1674,14 @@ sub AlterPriority {
if ( $where eq 'up' ) {
return unless $prev_priority;
_FixPriority( { reserve_id => $reserve_id, rank => $prev_priority } );
FixPriority( { reserve_id => $reserve_id, rank => $prev_priority } );
} elsif ( $where eq 'down' ) {
return unless $next_priority;
_FixPriority( { reserve_id => $reserve_id, rank => $next_priority } );
FixPriority( { reserve_id => $reserve_id, rank => $next_priority } );
} elsif ( $where eq 'top' ) {
_FixPriority( { reserve_id => $reserve_id, rank => $first_priority } );
FixPriority( { reserve_id => $reserve_id, rank => $first_priority } );
} elsif ( $where eq 'bottom' ) {
_FixPriority( { reserve_id => $reserve_id, rank => $last_priority } );
FixPriority( { reserve_id => $reserve_id, rank => $last_priority } );
}
Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue( { biblio_ids => [ $hold->biblionumber ] } )
@ -1652,21 +1692,27 @@ sub AlterPriority {
=head2 ToggleLowestPriority
ToggleLowestPriority( $borrowernumber, $biblionumber );
ToggleLowestPriority( $reserve_id, $is_lowestPriority );
This function sets the lowestPriority field to true if is false, and false if it is true.
This function sets the lowestPriority field to true if is false, and false if it is true
and then reorders the holds on the record.
A hold toggled to lowest priority will go the bottom.
A hold toggled to NOT lowest priority will go to the bottom of other non-lowest priority holds.
=cut
sub ToggleLowestPriority {
my ($reserve_id) = @_;
my ( $reserve_id, $is_lowestPriority ) = @_;
my $dbh = C4::Context->dbh;
my $rank = $is_lowestPriority ? undef : '999999';
my $sth = $dbh->prepare("UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
$sth->execute($reserve_id);
_FixPriority( { reserve_id => $reserve_id, rank => '999999' } );
FixPriority( { reserve_id => $reserve_id, rank => $rank } );
}
=head2 SuspendAll
@ -1709,17 +1755,16 @@ sub SuspendAll {
}
}
=head2 _FixPriority
=head2 FixPriority
_FixPriority({
FixPriority({
reserve_id => $reserve_id,
[rank => $rank,]
[ignoreSetLowestRank => $ignoreSetLowestRank]
});
or
_FixPriority({ biblionumber => $biblionumber});
FixPriority({ biblionumber => $biblionumber});
This routine adjusts the priority of a hold request and holds
on the same bib.
@ -1736,116 +1781,114 @@ then have reserves.priority set so that the first non-captured hold
has its priority set to 1, the second non-captured hold has its priority
set to 2, and so forth.
In both cases, holds that have the lowestPriority flag on are have their
In both cases, holds that have the lowestPriority flag on have their
priority adjusted to ensure that they remain at the end of the line.
Note that the ignoreSetLowestRank parameter is meant to be used only
when _FixPriority calls itself.
=cut
sub _FixPriority {
my ($params) = @_;
my $reserve_id = $params->{reserve_id};
my $rank = $params->{rank} // '';
my $ignoreSetLowestRank = $params->{ignoreSetLowestRank};
my $biblionumber = $params->{biblionumber};
sub FixPriority {
my ($params) = @_;
my $dbh = C4::Context->dbh;
Koha::Database->new->schema->txn_do(
sub {
my $reserve_id = $params->{reserve_id};
my $rank = $params->{rank} // '';
my $biblionumber = $params->{biblionumber};
my $hold;
if ($reserve_id) {
$hold = Koha::Holds->find($reserve_id);
if ( !defined $hold ) {
my $dbh = C4::Context->dbh;
# may have already been checked out and hold fulfilled
require Koha::Old::Holds;
$hold = Koha::Old::Holds->find($reserve_id);
}
return unless $hold;
}
my $hold;
if ($reserve_id) {
$hold = Koha::Holds->find($reserve_id);
if ( !defined $hold ) {
unless ($biblionumber) { # FIXME This is a very weird API
$biblionumber = $hold->biblionumber;
}
if ( $rank eq "del" ) { # FIXME will crash if called without $hold
$hold->cancel;
} elsif ( $reserve_id && ( $rank eq "W" || $rank eq "0" ) ) {
# make sure priority for waiting or in-transit items is 0
my $query = "
UPDATE reserves
SET priority = 0
WHERE reserve_id = ?
AND found IN ('W', 'T', 'P')
";
my $sth = $dbh->prepare($query);
$sth->execute($reserve_id);
}
my @priority;
# get what's left
my $query = "
SELECT reserve_id, borrowernumber, reservedate
FROM reserves
WHERE biblionumber = ?
AND ((found <> 'W' AND found <> 'T' AND found <> 'P') OR found IS NULL)
ORDER BY priority ASC
";
my $sth = $dbh->prepare($query);
$sth->execute($biblionumber);
while ( my $line = $sth->fetchrow_hashref ) {
push( @priority, $line );
}
# FIXME This whole sub must be rewritten, especially to highlight what is done when reserve_id is not given
# To find the matching index
my $i;
my $key = -1; # to allow for 0 to be a valid result
for ( $i = 0 ; $i < @priority ; $i++ ) {
if ( $reserve_id && $reserve_id == $priority[$i]->{'reserve_id'} ) {
$key = $i; # save the index
last;
}
}
# if index exists in array then move it to new position
if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
my $moving_item = splice( @priority, $key, 1 );
$new_rank = scalar @priority if $new_rank > scalar @priority;
splice( @priority, $new_rank, 0, $moving_item );
}
# now fix the priority on those that are left....
$query = "
UPDATE reserves
SET priority = ?
WHERE reserve_id = ?
";
$sth = $dbh->prepare($query);
for ( my $j = 0 ; $j < @priority ; $j++ ) {
$sth->execute(
$j + 1,
$priority[$j]->{'reserve_id'}
);
}
unless ($ignoreSetLowestRank) {
$sth = $dbh->prepare(
"SELECT reserve_id FROM reserves WHERE lowestPriority = 1 AND biblionumber = ? ORDER BY priority");
$sth->execute($biblionumber);
while ( my $res = $sth->fetchrow_hashref() ) {
_FixPriority(
{
reserve_id => $res->{'reserve_id'},
rank => '999999',
ignoreSetLowestRank => 1
# may have already been checked out and hold fulfilled
require Koha::Old::Holds;
$hold = Koha::Old::Holds->find($reserve_id);
}
);
return unless $hold;
}
unless ($biblionumber) { # FIXME This is a very weird API
$biblionumber = $hold->biblionumber;
}
if ( $rank eq "del" ) { # FIXME will crash if called without $hold
$hold->cancel;
} elsif ( $reserve_id && ( $rank eq "W" || $rank eq "0" ) ) {
# make sure priority for waiting or in-transit items is 0
my $query = "
UPDATE reserves
SET priority = 0
WHERE reserve_id = ?
AND found IN ('W', 'T', 'P')
";
my $sth = $dbh->prepare($query);
$sth->execute($reserve_id);
}
my @priority;
# Lock all active holds for this bib so concurrent FixPriority calls
# for the same hold set cannot interleave their read-modify-write cycle.
# FOR UPDATE is effective inside the enclosing txn_do transaction.
my $query = "
SELECT reserve_id, borrowernumber, reservedate, lowestPriority
FROM reserves
WHERE biblionumber = ?
AND ((found <> 'W' AND found <> 'T' AND found <> 'P') OR found IS NULL)
ORDER BY lowestPriority ASC, priority ASC
FOR UPDATE
";
my $sth = $dbh->prepare($query);
$sth->execute($biblionumber);
while ( my $line = $sth->fetchrow_hashref ) {
push( @priority, $line );
}
# FIXME This whole sub must be rewritten, especially to highlight what is done when reserve_id is not given
# To find the matching index
my $i;
my $key = -1; # to allow for 0 to be a valid result
for ( $i = 0 ; $i < @priority ; $i++ ) {
if ( $reserve_id && $reserve_id == $priority[$i]->{'reserve_id'} ) {
$key = $i; # save the index
last;
}
}
# if this hold is marked lowest priority, we can only move it so far;
# cap rank to just after the last non-lowestPriority hold using the
# already-fetched @priority array (avoids a second DB query and stale data)
if ( $hold && $hold->lowestPriority && $rank ne 'del' && $rank > 0 ) {
my $non_lowest_count = scalar grep { !$_->{lowestPriority} } @priority;
$rank = $non_lowest_count + 1 if $non_lowest_count && $rank <= $non_lowest_count;
}
# if index exists in array then move it to new position
if ( $key > -1 && $rank ne 'del' && $rank && $rank > 0 ) {
my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
my $moving_item = splice( @priority, $key, 1 );
$new_rank = scalar @priority if $new_rank > scalar @priority;
splice( @priority, $new_rank, 0, $moving_item );
}
# now fix the priority on those that are left....
# only updating if changed
$query = "
UPDATE reserves
SET priority = ?
WHERE reserve_id = ? AND priority != ?
";
$sth = $dbh->prepare($query);
for ( my $j = 0 ; $j < @priority ; $j++ ) {
$sth->execute(
$j + 1,
$priority[$j]->{'reserve_id'}, $j + 1
);
}
}
}
);
}
=head2 _Findgroupreserve
@ -2054,25 +2097,24 @@ sub _koha_notify_reserve {
};
while ( my ( $mtt, $letter_code ) = each %{ $messagingprefs->{transports} } ) {
next
if (
if (
( $mtt eq 'email' and not $to_address ) # No email address
or ( $mtt eq 'sms' and not $patron->smsalertnumber ) # No SMS number
or ( $mtt eq 'itiva'
and C4::Context->preference('TalkingTechItivaPhoneNotification')
) # Notice is handled by TalkingTech_itiva_outbound.pl
or ( $mtt eq 'phone' and not $patron->phone ) # No phone number to call
);
)
{
unless ($notification_sent) {
$notification_sent++;
&$send_notification( 'print', 'HOLD' );
}
next;
}
&$send_notification( $mtt, $letter_code, $messagingprefs->{wants_digest} );
$notification_sent++;
}
#Making sure that a print notification is sent if no other transport types can be utilized.
if ( !$notification_sent ) {
&$send_notification( 'print', 'HOLD' );
}
}
=head2 _koha_notify_hold_changed
@ -2198,13 +2240,13 @@ sub MoveReserve {
my $lookahead = C4::Context->preference('ConfirmFutureHolds'); #number of days to look for future holds
my ( $restype, $res, undef ) = CheckReserves( $item, $lookahead );
my $hold;
if ( $res && $res->{borrowernumber} == $patron->borrowernumber ) {
my $hold = Koha::Holds->find( $res->{reserve_id} );
$hold->fill( { item_id => $item->id } );
$hold = Koha::Holds->find( $res->{reserve_id} );
} else {
# The item is reserved by someone else.
# Find this item in the reserves
# The next reserve is for someone else,
# Check if this patron has a reserve on this biblio/item
my $lookahead_date = output_pref(
{
@ -2212,7 +2254,7 @@ sub MoveReserve {
dateformat => 'iso', dateonly => 1
}
);
my $hold = $patron->holds->search(
$hold = $patron->holds->search(
{
biblionumber => $item->biblionumber,
reservedate => { '<=' => $lookahead_date },
@ -2220,18 +2262,41 @@ sub MoveReserve {
},
{ order_by => 'priority' }
)->next();
}
if ($hold) {
if ($hold) {
if ( $hold->itemnumber && $hold->itemnumber == $item->id ) {
# The item is reserved by the current patron
$hold->fill( { item_id => $item->id } );
}
} else {
my $controlbranch = $patron->branchcode;
if ( C4::Context->preference('ReservesControlBranch') eq 'ItemHomeLibrary' ) {
$controlbranch = $item->homebranch;
}
# If no rule is set, the default behavior is to fill the hold
my $fill_other_biblio_holds_policy = Koha::CirculationRules->get_effective_rule_value(
{
categorycode => undef,
itemtype => $item->itype,
branchcode => $controlbranch,
rule_name => 'fill_other_biblio_holds_policy',
}
) // 1;
$hold->fill( { item_id => $item->id } ) if $fill_other_biblio_holds_policy;
}
}
if ( $cancelreserve && $res ) {
$hold = Koha::Holds->find( $res->{reserve_id} );
if ( $cancelreserve eq 'revert' ) {
$hold->revert_found();
} elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) { # cancel reserves on this item
$hold->cancel;
if ($hold) {
if ( $cancelreserve eq 'revert' ) {
$hold->revert_found();
} else { # cancel reserves on this item
$hold->cancel;
}
}
}
}

View File

@ -358,7 +358,7 @@ sub end_patron_session {
sub pay_fee {
my (
$self, $patron_id, $patron_pwd, $fee_amt, $fee_type, $pay_type, $fee_id, $trans_id, $currency, $is_writeoff,
$disallow_overpayment, $register_id
$disallow_overpayment, $register_id, $inst_id
) = @_;
my $trans = C4::SIP::ILS::Transaction::FeePayment->new();
@ -372,7 +372,7 @@ sub pay_fee {
}
my $trans_result = $trans->pay(
$patron->{borrowernumber}, $fee_amt, $pay_type, $fee_id, $is_writeoff, $disallow_overpayment,
$register_id
$register_id, $inst_id
);
my $ok = $trans_result->{ok};
$trans->ok($ok);

View File

@ -106,7 +106,10 @@ sub new {
my $it = $item->effective_itemtype;
$self->{itemtype} = $it;
my $itemtype = Koha::Database->new()->schema()->resultset('Itemtype')->find($it);
$self->{sip_media_type} = $itemtype->sip_media_type() if $itemtype;
if ($itemtype) {
$self->{sip_media_type} = $itemtype->sip_media_type;
$self->{magnetic_media} = $itemtype->sip_magnetic;
}
# check if its on issue and if so get the borrower
my $issue = Koha::Checkouts->find( { itemnumber => $item->itemnumber } );

View File

@ -131,10 +131,17 @@ sub new {
}
# Get currency 3 chars max
my $currency = substr Koha::Acquisition::Currencies->get_active->currency, 0, 3;
my $currency = Koha::Acquisition::Currencies->get_active;
if ($currency) {
$currency = substr $currency->currency, 0, 3;
}
my $circ_blocked =
( C4::Context->preference('OverduesBlockCirc') ne "noblock" && defined $flags->{ODUES}->{itemlist} ) ? 1 : 0;
# Fetch holds once and derive hold_items and unavail_holds from the result
my @holds = $patron->holds->as_list;
{
no warnings; # any of these $kp->{fields} being concat'd could be undef
%ilspatron = (
@ -165,7 +172,7 @@ sub new {
screen_msg => 'Greetings from Koha. ' . $kp->{opacnote} . $fines_msg,
print_line => '',
items => [],
hold_items => $flags->{WAITING}->{itemlist},
hold_items => [ map { $_->unblessed } @holds ],
overdue_items => $flags->{ODUES}->{itemlist},
too_many_overdue => $circ_blocked,
fine_items => [],
@ -212,7 +219,7 @@ sub new {
while ( my $message = $patron_messages->next ) {
my $messagedt = dt_from_string( $message->message_date, 'iso' );
my $formatted_date = output_pref( { dt => $messagedt, dateonly => 1 } );
push @messages_array, $formatted_date . ": " . $message->message;
push @messages_array, $formatted_date . ": " . $message->message_without_newlines;
}
if (@messages_array) {
$ilspatron{screen_msg} .= " Messages for you: " . join( ' / ', @messages_array );
@ -220,7 +227,7 @@ sub new {
}
# FIXME: populate recall_items
$ilspatron{unavail_holds} = _get_outstanding_holds( $kp->{borrowernumber} );
$ilspatron{unavail_holds} = _get_outstanding_holds( \@holds );
my $pending_checkouts = $patron->pending_checkouts;
my @barcodes;
@ -241,6 +248,7 @@ sub new {
my %fields = (
id => 0,
borrowernumber => 0,
branchcode => 0,
name => 0,
address => 0,
email_addr => 0,
@ -691,12 +699,12 @@ sub _get_address {
}
sub _get_outstanding_holds {
my $borrowernumber = shift;
my $holds = shift;
my $patron = Koha::Patrons->find($borrowernumber);
my $holds = $patron->holds->search( { -or => [ { found => undef }, { found => { '!=' => 'W' } } ] } );
my @holds;
while ( my $hold = $holds->next ) {
foreach my $hold ( @{$holds} ) {
next if ( $hold->found // '' ) eq 'W';
my $item;
if ( $hold->itemnumber ) {
$item = $hold->item;
@ -917,9 +925,6 @@ __END__
{message} "Yes"
{itemlist} ref-to-array: list of overdue books
{itemlisttext} Text list of overdue items
{WAITING} Set if there are items available that the patron reserved
{message} Message to this effect
{itemlist} ref-to-array: list of available items
=cut

View File

@ -124,6 +124,7 @@ sub do_checkin {
unless $human_required || $checkin_blocked_by_holds;
if ($checked_in_ok) {
delete $messages->{TransferArrived};
delete $messages->{ItemLocationUpdated};
delete $messages->{NotIssued};
delete $messages->{LocalUse};

View File

@ -74,18 +74,23 @@ sub do_checkout {
last;
}
} else {
my $hold_pref = C4::Context->preference("AllowItemsOnHoldCheckoutSIP") || 0;
foreach my $confirmation ( keys %{$needsconfirmation} ) {
if ( $confirmation eq 'RENEW_ISSUE' ) {
$self->screen_msg("Item already checked out to you: renewing item.");
} elsif ( $confirmation eq 'RESERVED' and !C4::Context->preference("AllowItemsOnHoldCheckoutSIP") ) {
} elsif ( $confirmation eq 'RESERVED' ) {
if ( $hold_pref >= 1 ) {
next;
}
$self->screen_msg("Item is reserved for another patron upon return.");
$noerror = 0;
} elsif ( $confirmation eq 'RESERVED' and C4::Context->preference("AllowItemsOnHoldCheckoutSIP") ) {
next;
} elsif ( $confirmation eq 'RESERVE_WAITING'
or $confirmation eq 'TRANSFERRED'
or $confirmation eq 'PROCESSING' )
{
if ( $hold_pref >= 2 ) {
next;
}
$self->screen_msg("Item is on hold for another patron.");
$noerror = 0;
} elsif ( $confirmation eq 'ISSUED_TO_ANOTHER' and C4::Context->preference("AllowItemsOnLoanCheckoutSIP") )

View File

@ -50,6 +50,7 @@ sub pay {
my $is_writeoff = shift;
my $disallow_overpayment = shift;
my $register_id = shift;
my $inst_id = shift;
my $type = $is_writeoff ? 'WRITEOFF' : 'PAYMENT';
@ -66,6 +67,7 @@ sub pay {
payment_type => 'SIP' . $sip_type,
interface => C4::Context->interface,
cash_register => $register_id,
library_id => $inst_id,
};
if ($fee_id) {

View File

@ -55,6 +55,10 @@ my %transports = (
# Read configuration
#
my $config = C4::SIP::Sip::Configuration->new( $ARGV[0] );
# Per-process record of when this worker loaded $config. Not shared since $config is per-process.
our $sip2_config_read_timestamp = Koha::Caches->get_instance->get_from_cache('sip2_resource_last_modified');
my @params;
#
@ -87,7 +91,11 @@ if ( defined( $config->{'server-params'} ) ) {
# Add user and group to prevent warn from Net::Server.
push @params, 'user=' . $>;
push @params, 'group=' . $>;
# $) can contain supplementary groups like "1010 1010" -> Net::Server tries setgroups() and fails.
my ($egid) = split /\s+/, $);
$egid ||= $(; # fallback to real gid just in case
push @params, 'group=' . $egid;
#
# This is the main event.
@ -230,13 +238,15 @@ sub _config_up_to_date {
my $cache = Koha::Caches->get_instance();
my $sip2_resource_last_modified = $cache->get_from_cache("sip2_resource_last_modified");
my $sip2_config_read_timestamp = $cache->get_from_cache("sip2_config_read_timestamp");
unless ($sip2_resource_last_modified) {
siplog( "LOG_WARNING", "Couldn't find sip2_resource_last_modified, considering configuration not up to date" );
siplog( "LOG_INFO", "Couldn't find sip2_resource_last_modified, considering configuration not up to date" );
return 0;
}
return $sip2_config_read_timestamp >= $sip2_resource_last_modified;
return 0 unless defined $sip2_config_read_timestamp;
return $sip2_config_read_timestamp >= $sip2_resource_last_modified ? 1 : 0;
}
#
@ -256,15 +266,17 @@ sub process_request {
my ( $sockaddr, $port, $proto );
my $transport;
# Flush L1 before the freshness check so it sees the current shared value, not a stale one inherited at fork.
Koha::Caches->flush_L1_caches();
$self->{config} = $config;
unless ( $self->_config_up_to_date() ) {
$self->{config} = C4::SIP::Sip::Configuration->get_configuration( undef, $self->{config} );
$sip2_config_read_timestamp = Koha::Caches->get_instance->get_from_cache('sip2_resource_last_modified');
}
# Flushing L1 to make sure the request will be processed using the correct data
Koha::Caches->flush_L1_caches();
$self->{account} = undef; # Clear out the account from the last request, it may be different
$self->{ils} = undef; # And the ILS handle, so a reused worker can't reuse a prior session's connection
$self->{logger} = set_logger( Koha::Logger->get( { interface => 'sip' } ) );
# Flush previous MDCs to prevent accidentally leaking incorrect MDC-entries
@ -464,12 +476,12 @@ sub telnet_transport {
$pwd = get_clean_string($pwd);
siplog( "LOG_DEBUG", "telnet_transport 2: uid length %s, pwd length %s", length($uid), length($pwd) );
# Check if user is authorized for SIP access, then authenticate via login_core
if ( exists( $config->{accounts}->{$uid} ) ) {
# Only log the terminal in once login_core actually authenticates it
if ( exists( $config->{accounts}->{$uid} )
&& C4::SIP::Sip::MsgType::login_core( $self, $uid, $pwd ) )
{
$account = $config->{accounts}->{$uid};
if ( C4::SIP::Sip::MsgType::login_core( $self, $uid, $pwd ) ) {
last;
}
last;
}
siplog( "LOG_WARNING", "Invalid login attempt: '%s'", ( $uid || '' ) );
print("Invalid login$CRLF");
@ -584,8 +596,11 @@ sub read_request {
$raw_length = length $buffer;
$buffer =~ s/^\s*[^A-z0-9]+//s;
# Every line must start with a "real" character. Not whitespace, control chars, etc.
$buffer =~ s/[^A-z0-9]+$//s;
# Remove leading/trailing control characters and whitespace.
# SIP2 messages should be a single printable line; this defensively
# cleans up stray CR/LF or junk from buggy or long-lived client connections.
$buffer =~ s/^[[:cntrl:]\s]+//;
$buffer =~ s/[[:cntrl:]\s]+$//;
# Same for the end. Note this catches the problem some clients have sending empty fields at the end, like |||
$buffer =~ s/\015?\012//g; # Extra line breaks must die

View File

@ -97,6 +97,7 @@ sub add_field {
);
$value = '';
}
$value =~ s/\r\n|\r|\n/ /g; # CR or LF terminates a sip message
# Protect against them in sip text fields
@ -140,7 +141,7 @@ sub maybe_add {
}
return ( defined($value) && length($value) )
? add_field( $fid, $value )
? add_field( $fid, $value, $server )
: '';
}

View File

@ -41,13 +41,13 @@ my $parser = XML::Simple->new(
sub get_configuration {
my ( $class, $config_file, $current_config ) = @_;
my $cache = Koha::Caches->get_instance();
my $sip2_resource_last_modified = $cache->get_from_cache("sip2_resource_last_modified");
my $cache = Koha::Caches->get_instance();
if ($sip2_resource_last_modified) {
$cache->set_in_cache( 'sip2_config_read_timestamp', $sip2_resource_last_modified );
} else {
$cache->set_in_cache( 'sip2_config_read_timestamp', dt_from_string()->epoch );
unless ( $cache->get_from_cache("sip2_resource_last_modified") ) {
# Seed sip2_resource_last_modified so the configuration is not re-read
# from the database on every request while the value is missing.
$cache->set_in_cache( 'sip2_resource_last_modified', dt_from_string()->epoch );
}
my $cfg;

View File

@ -24,7 +24,8 @@ use Koha::Patrons;
use Koha::Patron::Attributes;
use Koha::Plugins;
use Koha::Items;
use Koha::DateUtils qw( output_pref );
use Koha::DateUtils qw( output_pref );
use Koha::TemplateUtils qw( process_tt );
use UNIVERSAL::can;
@ -516,6 +517,9 @@ sub build_patron_status {
$resp .= $patron->build_custom_field_string($server);
$resp .= $patron->build_patron_attributes_string($server);
my $branchcode =
$server->{account}->{patron_branchcode_in_ao} ? $patron->branchcode : $fields->{ (FID_INST_ID) };
$resp .= add_field( FID_INST_ID, $branchcode, $server );
} else {
# Invalid patron (cardnumber)
@ -533,9 +537,10 @@ sub build_patron_status {
and $resp .= add_field( FID_VALID_PATRON, 'N', $server );
$resp .= maybe_add( FID_SCREEN_MSG, INVALID_CARD, $server );
$resp .= add_field( FID_INST_ID, $fields->{ (FID_INST_ID) }, $server );
}
$resp .= add_field( FID_INST_ID, $fields->{ (FID_INST_ID) }, $server );
return $resp;
}
@ -621,10 +626,10 @@ sub handle_checkout {
$resp .= timestamp;
# Now for the variable fields
$resp .= add_field( FID_INST_ID, $inst, $server );
$resp .= add_field( FID_PATRON_ID, $patron_id, $server );
$resp .= add_field( FID_ITEM_ID, $item_id, $server );
$resp .= add_field( FID_TITLE_ID, $item->title_id, $server );
$resp .= add_field( FID_INST_ID, $inst, $server );
$resp .= add_field( FID_PATRON_ID, $patron_id, $server );
$resp .= add_field( FID_ITEM_ID, $item_id, $server );
$resp .= add_field( FID_TITLE_ID, _format_title( { item => $item, server => $server } ), $server );
if ( $item->due_date ) {
my $due_date =
$account->{format_due_date}
@ -659,7 +664,7 @@ sub handle_checkout {
# If the item is valid, provide the title, otherwise
# leave it blank
$resp .= add_field( FID_TITLE_ID, $item ? $item->title_id : '', $server );
$resp .= add_field( FID_TITLE_ID, $item ? _format_title( { item => $item, server => $server } ) : "", $server );
# Due date is required. Since it didn't get checked out,
# it's not due, so leave the date blank
@ -752,14 +757,14 @@ sub handle_checkin {
$resp .= 'U';
}
$resp .= $status->alert ? 'Y' : 'N';
$resp .= ( $status->alert || $item->magnetic_media ) ? 'Y' : 'N';
$resp .= timestamp;
$resp .= add_field( FID_INST_ID, $inst_id, $server );
$resp .= add_field( FID_ITEM_ID, $item_id, $server );
if ($item) {
$resp .= add_field( FID_PERM_LOCN, $item->permanent_location, $server );
$resp .= maybe_add( FID_TITLE_ID, $item->title_id, $server );
$resp .= maybe_add( FID_TITLE_ID, _format_title( { item => $item, server => $server } ), $server );
$resp .= $item->build_additional_item_fields_string($server);
} else {
$resp .= add_field( FID_PERM_LOCN, "", $server );
@ -928,19 +933,19 @@ sub login_core {
my $pwd = shift;
my $status = 1; # Assume it all works
# Clear any session state up front, including anything left over from a previous connection
# on this preforked worker, so no login_core exit path can leave the caller acting as an
# authenticated terminal. Only a successful api_auth below re-establishes the session.
delete $server->{$_} foreach qw( account ils institution policy sip_username sip_password );
# Check if this userid is authorized for SIP access
if ( !exists( $server->{config}->{accounts}->{$uid} ) ) {
siplog( "LOG_WARNING", "MsgType::login_core: SIP access not authorized for user '$uid'" );
return 0;
}
# Store the active account configuration
$server->{account} = $server->{config}->{accounts}->{$uid};
my $inst = $server->{account}->{institution};
$server->{institution} = $server->{config}->{institutions}->{$inst};
$server->{policy} = $server->{institution}->{policy};
$server->{sip_username} = $uid;
$server->{sip_password} = $pwd;
# Authenticate before storing the account on the session
my $inst = $server->{config}->{accounts}->{$uid}->{institution};
# Authenticate using Koha's internal authentication (checks hashed password in borrowers table)
my $auth_status = api_auth( $uid, $pwd, $inst );
@ -952,6 +957,13 @@ sub login_core {
return 0;
}
# Store the active account configuration now user has authenticated
$server->{account} = $server->{config}->{accounts}->{$uid};
$server->{institution} = $server->{config}->{institutions}->{$inst};
$server->{policy} = $server->{institution}->{policy};
$server->{sip_username} = $uid;
$server->{sip_password} = $pwd;
siplog( "LOG_INFO", "Successful login/auth for '%s' of '%s'", $server->{account}->{id}, $inst );
#
@ -1090,7 +1102,8 @@ sub handle_patron_info {
$resp .= add_count( 'patron_info/recall_items', scalar @{ $patron->recall_items } );
$resp .= add_count( 'patron_info/unavail_holds', scalar @{ $patron->unavail_holds } );
$resp .= add_field( FID_INST_ID, ( $ils->institution_id || 'SIP2' ), $server );
my $branchcode = $server->{account}->{patron_branchcode_in_ao} ? $patron->branchcode : $ils->institution_id;
$resp .= add_field( FID_INST_ID, ( $branchcode || 'SIP2' ), $server );
# while the patron ID we got from the SC is valid, let's
# use the one returned from the ILS, just in case...
@ -1124,13 +1137,6 @@ sub handle_patron_info {
$resp .= maybe_add( FID_FEE_AMT, $patron->fee_amount, $server );
$resp .= add_field( FID_FEE_LMT, $patron->fee_limit, $server );
# TODO: zero or more item details for 2.0 can go here:
# hold_items
# overdue_items
# charged_items
# fine_items
# recall_items
$resp .= summary_info( $ils, $patron, $summary, $start, $end, $server );
$resp .= maybe_add( FID_HOME_ADDR, $patron->address, $server );
@ -1237,8 +1243,8 @@ sub handle_fee_paid {
$ils->check_inst_id( $inst_id, "handle_fee_paid" );
my $pay_result = $ils->pay_fee(
$patron_id, $patron_pwd, $fee_amt, $fee_type, $pay_type, $fee_id, $trans_id, $currency,
$is_writeoff, $disallow_overpayment, $register_id
$patron_id, $patron_pwd, $fee_amt, $fee_type, $pay_type, $fee_id, $trans_id, $currency,
$is_writeoff, $disallow_overpayment, $register_id, $ils->institution
);
$status = $pay_result->{status};
my $pay_response = $pay_result->{pay_response};
@ -1253,7 +1259,7 @@ sub handle_fee_paid {
"auto_account_expired" =>
"Scheduled for automatic renewal and cannot be renewed because the patron's account has expired",
"auto_renew" => "Scheduled for automatic renewal",
"auto_too_much_oweing" => "Scheduled for automatic renewal",
"auto_too_much_owing" => "Scheduled for automatic renewal",
"on_reserve" => "On hold for another patron",
"patron_restricted" => "Patron is currently restricted",
"item_denied_renewal" => "Item is not allowed renewal",
@ -1338,8 +1344,8 @@ sub handle_item_information {
$resp .= maybe_add( FID_SCREEN_MSG, "Item is damaged", $server );
}
$resp .= add_field( FID_ITEM_ID, $item->id, $server );
$resp .= add_field( FID_TITLE_ID, $item->title_id, $server );
$resp .= add_field( FID_ITEM_ID, $item->id, $server );
$resp .= add_field( FID_TITLE_ID, _format_title( { item => $item, server => $server } ), $server );
$resp .= maybe_add( FID_MEDIA_TYPE, $item->sip_media_type, $server );
$resp .= maybe_add( FID_PERM_LOCN, $item->permanent_location, $server );
@ -1424,8 +1430,8 @@ sub handle_item_status_update {
$resp .= $status->ok ? '1' : '0';
$resp .= timestamp;
$resp .= add_field( FID_ITEM_ID, $item->id, $server );
$resp .= add_field( FID_TITLE_ID, $item->title_id, $server );
$resp .= add_field( FID_ITEM_ID, $item->id, $server );
$resp .= add_field( FID_TITLE_ID, _format_title( { item => $item, server => $server } ), $server );
$resp .= maybe_add( FID_ITEM_PROPS, $item->sip_item_properties, $server );
}
@ -1546,10 +1552,10 @@ sub handle_hold {
( $status->expiration_date )
and $resp .= maybe_add( FID_EXPIRATION, timestamp( $status->expiration_date ), $server );
$resp .= maybe_add( FID_QUEUE_POS, $status->queue_position, $server );
$resp .= maybe_add( FID_PICKUP_LOCN, $status->pickup_location, $server );
$resp .= maybe_add( FID_ITEM_ID, $status->item->id, $server );
$resp .= maybe_add( FID_TITLE_ID, $status->item->title_id, $server );
$resp .= maybe_add( FID_QUEUE_POS, $status->queue_position, $server );
$resp .= maybe_add( FID_PICKUP_LOCN, $status->pickup_location, $server );
$resp .= maybe_add( FID_ITEM_ID, $status->item->id, $server );
$resp .= maybe_add( FID_TITLE_ID, _format_title( { item => $status->item, server => $server } ), $server );
} else {
# Not ok. still need required fields
@ -1613,9 +1619,9 @@ sub handle_renew {
}
$resp .= sipbool( desensitize( { status => $status, patron => $patron, server => $server } ) );
$resp .= timestamp;
$resp .= add_field( FID_PATRON_ID, $patron->id, $server );
$resp .= add_field( FID_ITEM_ID, $item->id, $server );
$resp .= add_field( FID_TITLE_ID, $item->title_id, $server );
$resp .= add_field( FID_PATRON_ID, $patron->id, $server );
$resp .= add_field( FID_ITEM_ID, $item->id, $server );
$resp .= add_field( FID_TITLE_ID, _format_title( { item => $item, server => $server } ), $server );
if ( $item->due_date ) {
$resp .= add_field( FID_DUE_DATE, timestamp( $item->due_date ), $server );
} else {
@ -1636,10 +1642,13 @@ sub handle_renew {
# If we found the patron or the item, the return the ILS
# information, otherwise echo back the information we received
# from the terminal
$resp .= add_field( FID_PATRON_ID, $patron ? $patron->id : $patron_id, $server );
$resp .= add_field( FID_ITEM_ID, $item ? $item->id : $item_id, $server );
$resp .= add_field( FID_TITLE_ID, $item ? $item->title_id : $title_id, $server );
$resp .= add_field( FID_DUE_DATE, '', $server );
$resp .= add_field( FID_PATRON_ID, $patron ? $patron->id : $patron_id, $server );
$resp .= add_field( FID_ITEM_ID, $item ? $item->id : $item_id, $server );
$resp .= add_field(
FID_TITLE_ID, $item ? _format_title( { item => $item, server => $server } ) : $title_id,
$server
);
$resp .= add_field( FID_DUE_DATE, '', $server );
}
if ( $status->fee_amount ) {
@ -1895,6 +1904,19 @@ sub desensitize {
return 1;
}
sub _format_title {
my ($params) = @_;
my $server = $params->{server};
my $item = $params->{item};
if ( defined $server->{account}->{aj_field_template} && $server->{account}->{aj_field_template} ne '' ) {
return process_tt(
$server->{account}->{aj_field_template},
{ biblio => $item->{object}->biblio, item => $item->{object} }
);
} else {
return $item->title_id;
}
}
1;
__END__

View File

@ -88,6 +88,44 @@ my %scrubbertypes = (
},
],
},
opac_virtual_card => {
allow => [
qw(
div span h1 h2 h3 h4 h5 h6 p br
ul ol li dl dt dd
a img svg
strong b em i u
hr
)
],
rules => [
div => {
class => qr/^[\w\s\-_]+$/,
id => qr/^[\w\-_]+$/,
},
span => {
class => qr/^[\w\s\-_]+$/,
id => qr/^[\w\-_]+$/,
},
p => {
class => qr/^[\w\s\-_]+$/,
id => qr/^[\w\-_]+$/,
},
'svg' => {
'id' => qr/^[\w\-_]+$/,
'class' => qr/^[\w\s\-_]+$/,
'data-barcode' => qr/^[\w\-_]+$/,
'data-barcode-format' => qr/^[\w\-_]+$/,
},
'img' => {
'src' => qr/^(?:https?:\/\/|\/)/i,
'alt' => 1,
'class' => qr/^[\w\s\-_]+$/,
'id' => qr/^[\w\-_]+$/,
},
],
}
);
sub new {

View File

@ -35,8 +35,8 @@ BEGIN {
use C4::Context;
use C4::Biblio qw( TransformMarcToKoha GetMarcFromKohaField GetFrameworkCode GetAuthorisedValueDesc GetBiblioData );
use C4::Koha
qw( getFacets GetVariationsOfISBN GetNormalizedUPC GetNormalizedEAN GetNormalizedOCLCNumber GetNormalizedISBN getitemtypeimagelocation );
use C4::Koha qw( getFacets GetVariationsOfISBN GetNormalizedISBN getitemtypeimagelocation );
use Koha::Biblio::Metadata::Extractor;
use Koha::DateUtils;
use Koha::Libraries;
use Koha::SearchEngine::QueryBuilder;
@ -88,8 +88,7 @@ This function attempts to find duplicate records using a hard-coded, fairly simp
sub FindDuplicate {
my ($record) = @_;
my $dbh = C4::Context->dbh;
my $result = TransformMarcToKoha( { record => $record } );
my $result = TransformMarcToKoha( { record => $record } );
my $sth;
my $query;
@ -592,6 +591,8 @@ sub getRecords {
if ($sorted_facets) {
$f->{facets} = $sorted_facets;
}
} elsif ( C4::Context->preference('FacetOrder') eq 'Stringwise' ) {
@{ $f->{facets} } = sort { $a->{facet_label_value} cmp $b->{facet_label_value} } @{ $f->{facets} };
}
}
}
@ -1659,7 +1660,6 @@ Format results in a form suitable for passing to the template
# building the HTML output for the template
sub searchResults {
my ( $search_context, $searchdesc, $hits, $results_per_page, $offset, $scan, $marcresults, $xslt_variables ) = @_;
my $dbh = C4::Context->dbh;
my @newresults;
require C4::Items;
@ -1768,9 +1768,10 @@ sub searchResults {
my $oldbiblio = TransformMarcToKoha( { record => $marcrecord, limit_table => 'no_items' } );
$oldbiblio->{result_number} = $i + 1;
$oldbiblio->{normalized_upc} = GetNormalizedUPC( $marcrecord, $marcflavour );
$oldbiblio->{normalized_ean} = GetNormalizedEAN( $marcrecord, $marcflavour );
$oldbiblio->{normalized_oclc} = GetNormalizedOCLCNumber( $marcrecord, $marcflavour );
my $extractor = Koha::Biblio::Metadata::Extractor->new( { metadata => $marcrecord } );
$oldbiblio->{normalized_upc} = $extractor->get_normalized_upc;
$oldbiblio->{normalized_ean} = $extractor->get_normalized_ean;
$oldbiblio->{normalized_oclc} = $extractor->get_normalized_oclc;
$oldbiblio->{normalized_isbn} = GetNormalizedISBN( $oldbiblio->{isbn}, $marcrecord, $marcflavour )
; # Use existing ISBN from record if we got one
$oldbiblio->{content_identifier_exists} = 1
@ -1955,6 +1956,7 @@ sub searchResults {
# is item a waiting recall?
my $recallstatus = '';
# Check some statuses before checking transfer info, respect MaxSearchResultsItemsPerRecordStatusCheck
unless (
$item->{withdrawn}
|| $item->{itemlost}
@ -1965,20 +1967,12 @@ sub searchResults {
)
{
# A couple heuristics to limit how many times
# we query the database for item transfer information, sacrificing
# accuracy in some cases for speed;
#
# 1. don't query if item has one of the other statuses
# 2. don't check transit status if the bib has
# more than 20 items
#
# FIXME: to avoid having the query the database like this, and to make
# the in transit status count as unavailable for search limiting,
# should map transit status to record indexed in Zebra.
my $item_object = Koha::Items->find( $item->{itemnumber} );
my $transfer = defined($item_object) ? $item_object->get_transfer : undef;
# NOTE: We are bypassing an expected Koha::Item->get_transfer call here for speed.
# TODO: Could we index the transfer status ?
my $transfer = Koha::Item::Transfers->search(
{ itemnumber => $item->{itemnumber}, datearrived => undef, datecancelled => undef },
{ order_by => [ { '-desc' => 'datesent' }, { '-asc' => 'daterequested' } ] }
)->next;
( $transfertwhen, $transfertfrom, $transfertto ) = defined($transfer)
? (
$transfer->datesent, $transfer->frombranch,
@ -1986,7 +1980,7 @@ sub searchResults {
)
: ( '', '', '' );
$reservestatus = C4::Reserves::GetReserveStatus( $item->{itemnumber} );
if ( C4::Context->preference('UseRecalls') ) {
if ( C4::Context->preference('UseRecalls') ne "off" ) {
if ( Koha::Recalls->search( { item_id => $item->{itemnumber}, status => 'waiting' } )->count ) {
$recallstatus = 'Waiting';
}

View File

@ -325,7 +325,8 @@ sub GetFullSubscription {
aqbooksellers.name as aqbooksellername,
biblio.title as bibliotitle,
subscription.branchcode AS branchcode,
subscription.subscriptionid AS subscriptionid
subscription.subscriptionid AS subscriptionid,
subscription.preselect_issues_in_collections_table AS preselect_issues_in_collections_table
FROM serial
LEFT JOIN subscription ON
(serial.subscriptionid=subscription.subscriptionid )
@ -351,13 +352,13 @@ sub GetFullSubscription {
=head2 PrepareSerialsData
$array_ref = PrepareSerialsData($serialinfomation)
where serialinformation is a hashref array
$array_ref = PrepareSerialsData($serialinfomation, $add_itemnumbers)
where serialinformation is a hashref array and add_itemnumbers is a boolean
=cut
sub PrepareSerialsData {
my ($lines) = @_;
my ( $lines, $add_itemnumbers ) = @_;
return unless ($lines);
@ -370,9 +371,15 @@ sub PrepareSerialsData {
foreach my $subs ( @{$lines} ) {
$subs->{ "status" . $subs->{'status'} } = 1;
if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
if ( $subs->{preselect_issues_in_collections_table} && grep { $_ == $subs->{status} }
( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) )
{
$subs->{"checked"} = 1;
}
if ($add_itemnumbers) {
my $serialitem = Koha::Serial::Items->find( { serialid => $subs->{'serialid'} } );
$subs->{"itemnumber"} = $serialitem->itemnumber if $serialitem;
}
if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
$year = $subs->{'year'};
@ -466,7 +473,8 @@ sub GetFullSubscriptionsFromBiblionumber {
biblio.title as bibliotitle,
subscription.branchcode AS branchcode,
subscription.subscriptionid AS subscriptionid,
subscription.location AS location
subscription.location AS location,
subscription.preselect_issues_in_collections_table AS preselect_issues_in_collections_table
FROM serial
LEFT JOIN subscription ON
(serial.subscriptionid=subscription.subscriptionid)
@ -810,7 +818,7 @@ sub GetPreviousSerialid {
my (
$nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
$newinnerloop1, $newinnerloop2, $newinnerloop3
) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate, $count_forward );
) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate, $nextpublisheddate, $count_forward );
$subscription is a hashref containing all the attributes of the table
'subscription'.
@ -818,13 +826,14 @@ $pattern is a hashref containing all the attributes of the table
'subscription_numberpatterns'.
$frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
$planneddate is a date string in iso format.
$nextpublishedddate is a date string in iso format.
$count_forward is the number of issues to count forward, defaults to 1 if omitted
This function get the next issue for the subscription given on input arg
=cut
sub GetNextSeq {
my ( $subscription, $pattern, $frequency, $planneddate, $count_forward ) = @_;
my ( $subscription, $pattern, $frequency, $planneddate, $nextpublisheddate, $count_forward ) = @_;
return unless ( $subscription and $pattern );
@ -915,7 +924,7 @@ sub GetNextSeq {
$calculated =~ s/\{Z\}/$newlastvalue3string/g;
}
my $dt = dt_from_string($planneddate);
my $dt = dt_from_string($nextpublisheddate);
$calculated =~ s/\{Month\}/$dt->month/eg;
$calculated =~ s/\{MonthName\}/$dt->month_name/eg;
$calculated =~ s/\{Year\}/$dt->year/eg;
@ -1167,15 +1176,16 @@ sub ModSerialStatus {
my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern( $subscription->{numberpattern} );
my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency( $subscription->{periodicity} );
# next date (calculated from actual date & frequency parameters)
my $nextpublisheddate = GetNextDate( $subscription, $publisheddate, $frequency, 1 );
my $nextpubdate = $nextpublisheddate;
# next issue number
my (
$newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
$newinnerloop1, $newinnerloop2, $newinnerloop3
) = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate, $count );
) = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate, $nextpublisheddate, $count );
# next date (calculated from actual date & frequency parameters)
my $nextpublisheddate = GetNextDate( $subscription, $publisheddate, $frequency, 1 );
my $nextpubdate = $nextpublisheddate;
$query =
"UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
WHERE subscriptionid = ?";
@ -1207,7 +1217,7 @@ sub _handle_seqno {
my ( $seq, $list, $op ) = @_; # op = ADD | REMOVE | CHECK (default: ADD)
my $seq_r = $seq;
$seq_r =~ s/([()])/\\$1/g; # Adjust disturbing parentheses for regex, maybe extend in future
$seq_r =~ s/([(){}])/\\$1/g; # Adjust disturbing parentheses for regex, maybe extend in future
if ( !$op or $op eq 'ADD' ) {
$list .= "; $seq" if $list !~ /(^|;)\s*$seq_r(?=;|$)/;
@ -1337,58 +1347,59 @@ returns the number of rows affected
sub ModSubscription {
my (
$auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
$periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
$numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
$lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
$biblionumber, $callnumber, $notes, $letter, $manualhistory,
$internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
$graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
$itemtype, $previousitemtype, $mana_id, $ccode, $published_on_template
$auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
$periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
$numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
$lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
$biblionumber, $callnumber, $notes, $letter, $manualhistory,
$internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
$graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
$itemtype, $previousitemtype, $mana_id, $ccode, $published_on_template, $preselect_issues_in_collections_table
) = @_;
my $subscription = Koha::Subscriptions->find($subscriptionid);
$subscription->set(
{
librarian => $auser,
branchcode => $branchcode,
aqbooksellerid => $aqbooksellerid,
cost => $cost,
aqbudgetid => $aqbudgetid,
biblionumber => $biblionumber,
startdate => $startdate,
periodicity => $periodicity,
numberlength => $numberlength,
weeklength => $weeklength,
monthlength => $monthlength,
lastvalue1 => $lastvalue1,
innerloop1 => $innerloop1,
lastvalue2 => $lastvalue2,
innerloop2 => $innerloop2,
lastvalue3 => $lastvalue3,
innerloop3 => $innerloop3,
status => $status,
notes => $notes,
letter => $letter,
firstacquidate => $firstacquidate,
irregularity => $irregularity,
numberpattern => $numberpattern,
locale => $locale,
callnumber => $callnumber,
manualhistory => $manualhistory,
internalnotes => $internalnotes,
serialsadditems => $serialsadditems,
staffdisplaycount => $staffdisplaycount,
opacdisplaycount => $opacdisplaycount,
graceperiod => $graceperiod,
location => $location,
enddate => $enddate,
skip_serialseq => $skip_serialseq,
itemtype => $itemtype,
previousitemtype => $previousitemtype,
mana_id => $mana_id,
ccode => $ccode,
published_on_template => $published_on_template,
librarian => $auser,
branchcode => $branchcode,
aqbooksellerid => $aqbooksellerid,
cost => $cost,
aqbudgetid => $aqbudgetid,
biblionumber => $biblionumber,
startdate => $startdate,
periodicity => $periodicity,
numberlength => $numberlength,
weeklength => $weeklength,
monthlength => $monthlength,
lastvalue1 => $lastvalue1,
innerloop1 => $innerloop1,
lastvalue2 => $lastvalue2,
innerloop2 => $innerloop2,
lastvalue3 => $lastvalue3,
innerloop3 => $innerloop3,
status => $status,
notes => $notes,
letter => $letter,
firstacquidate => $firstacquidate,
irregularity => $irregularity,
numberpattern => $numberpattern,
locale => $locale,
callnumber => $callnumber,
manualhistory => $manualhistory,
internalnotes => $internalnotes,
serialsadditems => $serialsadditems,
staffdisplaycount => $staffdisplaycount,
opacdisplaycount => $opacdisplaycount,
graceperiod => $graceperiod,
location => $location,
enddate => $enddate,
skip_serialseq => $skip_serialseq,
itemtype => $itemtype,
previousitemtype => $previousitemtype,
mana_id => $mana_id,
ccode => $ccode,
published_on_template => $published_on_template,
preselect_issues_in_collections_table => $preselect_issues_in_collections_table || 0,
}
)->store;
@ -1421,58 +1432,59 @@ the id of this new subscription
sub NewSubscription {
my (
$auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
$startdate, $periodicity, $numberlength, $weeklength, $monthlength,
$lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
$innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
$numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
$serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
$location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id, $ccode,
$published_on_template,
$auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
$startdate, $periodicity, $numberlength, $weeklength, $monthlength,
$lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
$innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
$numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
$serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
$location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id, $ccode,
$published_on_template, $preselect_issues_in_collections_table
) = @_;
my $dbh = C4::Context->dbh;
my $subscription = Koha::Subscription->new(
{
librarian => $auser,
branchcode => $branchcode,
aqbooksellerid => $aqbooksellerid,
cost => $cost,
aqbudgetid => $aqbudgetid,
biblionumber => $biblionumber,
startdate => $startdate,
periodicity => $periodicity,
numberlength => $numberlength,
weeklength => $weeklength,
monthlength => $monthlength,
lastvalue1 => $lastvalue1,
innerloop1 => $innerloop1,
lastvalue2 => $lastvalue2,
innerloop2 => $innerloop2,
lastvalue3 => $lastvalue3,
innerloop3 => $innerloop3,
status => $status,
notes => $notes,
letter => $letter,
firstacquidate => $firstacquidate,
irregularity => $irregularity,
numberpattern => $numberpattern,
locale => $locale,
callnumber => $callnumber,
manualhistory => $manualhistory,
internalnotes => $internalnotes,
serialsadditems => $serialsadditems,
staffdisplaycount => $staffdisplaycount,
opacdisplaycount => $opacdisplaycount,
graceperiod => $graceperiod,
location => $location,
enddate => $enddate,
skip_serialseq => $skip_serialseq,
itemtype => $itemtype,
previousitemtype => $previousitemtype,
mana_id => $mana_id,
ccode => $ccode,
published_on_template => $published_on_template,
librarian => $auser,
branchcode => $branchcode,
aqbooksellerid => $aqbooksellerid,
cost => $cost,
aqbudgetid => $aqbudgetid,
biblionumber => $biblionumber,
startdate => $startdate,
periodicity => $periodicity,
numberlength => $numberlength,
weeklength => $weeklength,
monthlength => $monthlength,
lastvalue1 => $lastvalue1,
innerloop1 => $innerloop1,
lastvalue2 => $lastvalue2,
innerloop2 => $innerloop2,
lastvalue3 => $lastvalue3,
innerloop3 => $innerloop3,
status => $status,
notes => $notes,
letter => $letter,
firstacquidate => $firstacquidate,
irregularity => $irregularity,
numberpattern => $numberpattern,
locale => $locale,
callnumber => $callnumber,
manualhistory => $manualhistory,
internalnotes => $internalnotes,
serialsadditems => $serialsadditems,
staffdisplaycount => $staffdisplaycount,
opacdisplaycount => $opacdisplaycount,
graceperiod => $graceperiod,
location => $location,
enddate => $enddate,
skip_serialseq => $skip_serialseq,
itemtype => $itemtype,
previousitemtype => $previousitemtype,
mana_id => $mana_id,
ccode => $ccode,
published_on_template => $published_on_template,
preselect_issues_in_collections_table => $preselect_issues_in_collections_table || 0,
}
)->store;
$subscription->discard_changes;

View File

@ -28,7 +28,7 @@ BEGIN {
use C4::Biblio qw( GetAuthorisedValueDesc );
use C4::Context;
use C4::Koha qw( GetNormalizedUPC GetNormalizedOCLCNumber GetNormalizedISBN GetNormalizedEAN );
use C4::Koha qw( GetNormalizedISBN );
use Koha::Biblios;
use Koha::Libraries;
@ -235,10 +235,10 @@ sub GetShelfInfo {
$item->{part_number} = $biblio->part_number;
$item->{part_name} = $biblio->part_name;
my $this_record = $biblio->metadata->record;
$item->{'browser_normalized_upc'} = GetNormalizedUPC( $this_record, $marcflavour );
$item->{'browser_normalized_oclc'} = GetNormalizedOCLCNumber( $this_record, $marcflavour );
$item->{'browser_normalized_upc'} = $biblio->normalized_upc;
$item->{'browser_normalized_oclc'} = $biblio->normalized_oclc;
$item->{'browser_normalized_isbn'} = GetNormalizedISBN( undef, $this_record, $marcflavour );
$item->{'browser_normalized_ean'} = GetNormalizedEAN( $this_record, $marcflavour );
$item->{'browser_normalized_ean'} = $biblio->normalized_ean;
push @valid_items, $item;
}
return @valid_items;

View File

@ -110,7 +110,7 @@ sub update_data {
$i++;
}
}
say "$i data insered or updated";
say "$i data inserted or updated";
}
=head2 get_report

View File

@ -23,16 +23,10 @@ use base 'Exporter';
BEGIN {
our @EXPORT = qw(
ConnectSuggestionAndBiblio
DelSuggestion
GetSuggestion
GetSuggestionByStatus
GetSuggestionFromBiblionumber
GetSuggestionInfoFromBiblionumber
GetSuggestionInfo
ModStatus
ModSuggestion
DelSuggestionsOlderThan
GetUnprocessedSuggestions
MarcRecordFromNewSuggestion
);
@ -73,168 +67,6 @@ Suggestions done by other borrowers can be seen when not "AVAILABLE"
=head1 FUNCTIONS
=head2 GetSuggestion
\%sth = &GetSuggestion($suggestionid)
this function get the detail of the suggestion $suggestionid (input arg)
return :
the result of the SQL query as a hash : $sth->fetchrow_hashref.
=cut
sub GetSuggestion {
my ($suggestionid) = @_;
my $dbh = C4::Context->dbh;
my $query = q{
SELECT *
FROM suggestions
WHERE suggestionid=?
};
my $sth = $dbh->prepare($query);
$sth->execute($suggestionid);
return ( $sth->fetchrow_hashref );
}
=head2 GetSuggestionFromBiblionumber
$ordernumber = &GetSuggestionFromBiblionumber($biblionumber)
Get a suggestion from it's biblionumber.
return :
the id of the suggestion which is related to the biblionumber given on input args.
=cut
sub GetSuggestionFromBiblionumber {
my ($biblionumber) = @_;
my $query = q{
SELECT suggestionid
FROM suggestions
WHERE biblionumber=? LIMIT 1
};
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare($query);
$sth->execute($biblionumber);
my ($suggestionid) = $sth->fetchrow;
return $suggestionid;
}
=head2 GetSuggestionInfoFromBiblionumber
Get a suggestion and borrower's information from it's biblionumber.
return :
all information (suggestion and borrower) of the suggestion which is related to the biblionumber given.
=cut
sub GetSuggestionInfoFromBiblionumber {
my ($biblionumber) = @_;
my $query = q{
SELECT suggestions.*,
U1.surname AS surnamesuggestedby,
U1.firstname AS firstnamesuggestedby,
U1.borrowernumber AS borrnumsuggestedby
FROM suggestions
LEFT JOIN borrowers AS U1 ON suggestedby=U1.borrowernumber
WHERE biblionumber=?
LIMIT 1
};
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare($query);
$sth->execute($biblionumber);
return $sth->fetchrow_hashref;
}
=head2 GetSuggestionInfo
Get a suggestion and borrower's information from it's suggestionid
return :
all information (suggestion and borrower) of the suggestion which is related to the suggestionid given.
=cut
sub GetSuggestionInfo {
my ($suggestionid) = @_;
my $query = q{
SELECT suggestions.*,
U1.surname AS surnamesuggestedby,
U1.firstname AS firstnamesuggestedby,
U1.borrowernumber AS borrnumsuggestedby
FROM suggestions
LEFT JOIN borrowers AS U1 ON suggestedby=U1.borrowernumber
WHERE suggestionid=?
LIMIT 1
};
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare($query);
$sth->execute($suggestionid);
return $sth->fetchrow_hashref;
}
=head2 GetSuggestionByStatus
$aqorders = &GetSuggestionByStatus($status,[$branchcode])
Get a suggestion from it's status
return :
all the suggestion with C<$status>
=cut
sub GetSuggestionByStatus {
my $status = shift;
my $branchcode = shift;
my $dbh = C4::Context->dbh;
my @sql_params = ($status);
my $query = q{
SELECT suggestions.*,
U1.surname AS surnamesuggestedby,
U1.firstname AS firstnamesuggestedby,
U1.branchcode AS branchcodesuggestedby,
B1.branchname AS branchnamesuggestedby,
U1.borrowernumber AS borrnumsuggestedby,
U1.categorycode AS categorycodesuggestedby,
C1.description AS categorydescriptionsuggestedby,
U2.surname AS surnamemanagedby,
U2.firstname AS firstnamemanagedby,
U2.borrowernumber AS borrnummanagedby
FROM suggestions
LEFT JOIN borrowers AS U1 ON suggestedby=U1.borrowernumber
LEFT JOIN borrowers AS U2 ON managedby=U2.borrowernumber
LEFT JOIN categories AS C1 ON C1.categorycode=U1.categorycode
LEFT JOIN branches AS B1 on B1.branchcode=U1.branchcode
WHERE status = ?
ORDER BY suggestionid
};
# filter on branch
if ( C4::Context->preference("IndependentBranches") || $branchcode ) {
my $userenv = C4::Context->userenv;
if ($userenv) {
unless ( C4::Context->IsSuperLibrarian() ) {
push @sql_params, $userenv->{branch};
$query .= q{ AND (U1.branchcode = ? OR U1.branchcode ='') };
}
}
if ($branchcode) {
push @sql_params, $branchcode;
$query .= q{ AND (U1.branchcode = ? OR U1.branchcode ='') };
}
}
my $sth = $dbh->prepare($query);
$sth->execute(@sql_params);
my $results;
$results = $sth->fetchall_arrayref( {} );
return $results;
}
=head2 ModSuggestion
&ModSuggestion($suggestion)
@ -267,14 +99,14 @@ sub ModSuggestion {
{
# fetch the entire updated suggestion so that we can populate the letter
my $full_suggestion = GetSuggestion( $suggestion->{suggestionid} );
my $full_suggestion = Koha::Suggestions->find( $suggestion->{suggestionid} )->unblessed();
my $patron = Koha::Patrons->find( $full_suggestion->{suggestedby} );
my $transport =
( C4::Context->preference("FallbackToSMSIfNoEmail") )
&& ( $patron->smsalertnumber )
&& ( !$patron->email ) ? 'sms' : 'email';
&& ( !$patron->notice_email_address ) ? 'sms' : 'email';
if (
my $letter = C4::Letters::GetPreparedLetter(
@ -306,26 +138,6 @@ sub ModSuggestion {
return 1; # No useful if the exception is raised earlier
}
=head2 ConnectSuggestionAndBiblio
&ConnectSuggestionAndBiblio($ordernumber,$biblionumber)
connect a suggestion to an existing biblio
=cut
sub ConnectSuggestionAndBiblio {
my ( $suggestionid, $biblionumber ) = @_;
my $dbh = C4::Context->dbh;
my $query = q{
UPDATE suggestions
SET biblionumber=?
WHERE suggestionid=?
};
my $sth = $dbh->prepare($query);
$sth->execute( $biblionumber, $suggestionid );
}
=head2 DelSuggestion
&DelSuggestion($borrowernumber,$ordernumber)
@ -351,41 +163,20 @@ sub DelSuggestion {
$borrowernumber //= '';
if ( defined $type && $type eq 'intranet' || $suggestedby eq $borrowernumber ) {
my $suggestion = Koha::Suggestions->find($suggestionid);
if ( C4::Context->preference("SuggestionsLog") ) {
logaction( 'SUGGESTION', 'DELETE', $suggestionid, $suggestion, undef, $suggestion );
}
my $queryDelete = q{
DELETE FROM suggestions
WHERE suggestionid=?
};
$sth = $dbh->prepare($queryDelete);
my $suggestiondeleted = $sth->execute($suggestionid);
if ( C4::Context->preference("SuggestionsLog") ) {
logaction( 'SUGGESTION', 'DELETE', $suggestionid, '' );
}
return $suggestiondeleted;
}
}
=head2 DelSuggestionsOlderThan
&DelSuggestionsOlderThan($days)
Delete all suggestions older than TODAY-$days , that have be accepted or rejected.
We do now allow a negative number. If you want to delete all suggestions, just use Koha::Suggestions->delete or so.
=cut
sub DelSuggestionsOlderThan {
my ($days) = @_;
return unless $days && $days > 0;
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare(
q{
DELETE FROM suggestions
WHERE STATUS<>'ASKED'
AND manageddate < ADDDATE(NOW(), ?)
}
);
$sth->execute("-$days");
}
=head2 GetUnprocessedSuggestions
Missing POD for GetUnprocessedSuggestions.

View File

@ -22,6 +22,8 @@ use Modern::Perl;
use base 'Exporter';
use C4::Context;
use Module::Load::Conditional qw( check_install );
BEGIN {
our @EXPORT_OK = qw(
get_tags get_tag_rows
@ -54,7 +56,6 @@ BEGIN {
use Carp qw( carp );
use Module::Load::Conditional qw( check_install );
use Koha::Tags;
use Koha::Tags::Approvals;
use Koha::Tags::Indexes;

View File

@ -179,10 +179,6 @@ C4::Templates - Functions for managing templates
=cut
# FIXME: this is a quick fix to stop rc1 installing broken
# Still trying to figure out the correct fix.
my $path = C4::Context->config('intrahtdocs') . "/prog/en/includes/";
#---------------------------------------------------------------------------------------------------------
# FIXME - POD

View File

@ -160,11 +160,11 @@ sub _shared_preferences {
PrefillItem
z3950NormalizeAuthor
SpineLabelAutoPrint
SpineLabelShowPrintOnBibDetails
BlockReturnOfLostItems
BlockReturnOfWithdrawnItems
CalculateFinesOnReturn
AgeRestrictionOverride
PatronAgeRestriction
AllFinesNeedOverride
AllowFineOverride
AllowItemsOnHoldCheckoutSIP
@ -180,7 +180,8 @@ sub _shared_preferences {
IssueLostItem
IssuingInProcess
OverduesBlockCirc
RenewalPeriodBase
ManualRenewalPeriodBase
AutomaticRenewalPeriodBase
RenewalSendNotice
ReturnBeforeExpiry
TransfersMaxDaysWarning
@ -193,7 +194,14 @@ sub _shared_preferences {
RefundLostOnReturnControl
WhenLostChargeReplacementFee
WhenLostForgiveFine
FineNoRenewals
FineNoRenewalsBlockAutoRenew
FineNoRenewalsBlockSelfCheckRenew
FineNoRenewalsIncludeCredits
AllowHoldDateInFuture
OPACAllowHoldDateInFuture
ConfirmFutureHolds
FutureHoldsBlockRenewals
AllowHoldItemTypeSelection
AllowHoldPolicyOverride
AllowHoldsOnDamagedItems
@ -204,7 +212,6 @@ sub _shared_preferences {
DisplayMultiPlaceHold
emailLibrarianWhenHoldIsPlaced
ExpireReservesMaxPickUpDelay
OPACAllowHoldDateInFuture
OPACAllowUserToChooseBranch
ReservesControlBranch
ReservesNeedReturns
@ -234,7 +241,7 @@ sub _shared_preferences {
AmazonCoverImages
OPACAmazonCoverImages
Babeltheque
BakerTaylorEnabled
EnableZotero
GoogleJackets
HTML5MediaEnabled
LibraryThingForLibrariesEnabled
@ -314,6 +321,7 @@ sub _shared_preferences {
autoMemberNum
BorrowerRenewalPeriodBase
EnableBorrowerFiles
LimitReportsByLibrary
EnhancedMessagingPreferences
ExtendedPatronAttributes
intranetreadinghistory
@ -351,6 +359,7 @@ sub _shared_preferences {
ILS-DI
OAI-PMH
version
UseRecalls
/;
return \@preferences;

View File

@ -16,7 +16,7 @@ sub search {
my $start = $params->{start};
my $length = $params->{length};
# If not logged in user, be carreful and set the borrowernumber to 0
# If not logged in user, be careful and set the borrowernumber to 0
# to prevent private lists lack
my $loggedinuser = C4::Context->userenv->{'number'} || 0;

View File

@ -51,7 +51,6 @@ This module provides facilities for retrieving ThingISBN and XISBN content in Ko
sub _get_biblio_from_xisbn {
my $xisbn = shift;
my $dbh = C4::Context->dbh;
my $searcher = Koha::SearchEngine::Search->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
my ( $errors, $results, $total_hits ) = $searcher->simple_search_compat( "nb=$xisbn", 0, 1 );

View File

@ -39,6 +39,7 @@ use C4::Koha qw( xml_escape );
use C4::Biblio qw( GetAuthorisedValueDesc GetFrameworkCode GetMarcStructure );
use Koha::AuthorisedValues;
use Koha::ItemTypes;
use Koha::Plugins;
use Koha::RecordProcessor;
use Koha::Libraries;
use Koha::Recalls;
@ -65,6 +66,12 @@ The helper function _get_best_default_xslt_filename is used in a unit test.
=cut
=head2 _get_best_default_xslt_filename
Find the default file, trying a few standard variations, until the file is located
=cut
sub _get_best_default_xslt_filename {
my ( $htdocs, $theme, $lang, $base_xslfile ) = @_;
@ -197,10 +204,23 @@ sub XSLTParse4Display {
my $xslfilename = get_xsl_filename($xslsyspref);
my $frameworkcode = GetFrameworkCode($biblionumber) || '';
my $frameworkcode = GetFrameworkCode($biblionumber) || '';
my $filters = ['ExpandCodedFields'];
Koha::Plugins->call(
'xslt_record_processor_filters',
{
filters => $filters,
interface => $interface,
frameworkcode => $frameworkcode,
xsl_syspref => $xslsyspref,
biblionumber => $biblionumber,
}
);
my $record_processor = Koha::RecordProcessor->new(
{
filters => ['ExpandCodedFields'],
filters => $filters,
options => {
interface => $interface,
frameworkcode => $frameworkcode
@ -365,7 +385,7 @@ sub buildKohaItemsNamespace {
my $substatus = '';
my $recalls_count;
if ( C4::Context->preference('UseRecalls') ) {
if ( C4::Context->preference('UseRecalls') ne "off" ) {
$recalls_count = Koha::Recalls->search( { item_id => $item->itemnumber, status => 'waiting' } )->count;
}

View File

@ -29,7 +29,7 @@ use vars qw{ $VERSION };
# - #4 : the developer version. The 4th number is the database subversion.
# used by developers when the database changes. updatedatabase take care of the changes itself
# and is automatically called by Auth.pm when needed.
$VERSION = "25.11.00.000";
$VERSION = "26.06.00.030";
sub version {
return $VERSION;

View File

@ -25,7 +25,6 @@ use Try::Tiny qw( catch try );
use C4::Letters;
use C4::Log qw( logaction );
use C4::Stats qw( UpdateStats );
use C4::Overdues qw(GetFine);
use C4::Context;
@ -38,6 +37,7 @@ use Koha::Account::DebitTypes;
use Koha::Exceptions;
use Koha::Exceptions::Account;
use Koha::Plugins;
use Koha::Statistics;
=head1 NAME
@ -286,7 +286,7 @@ sub add_credit {
}
)->store();
C4::Stats::UpdateStats(
Koha::Statistic->new(
{
branch => $library_id,
type => lc($credit_type),
@ -294,7 +294,8 @@ sub add_credit {
borrowernumber => $self->{patron_id},
interface => $interface,
}
) if grep { $credit_type eq $_ } ( 'PAYMENT', 'WRITEOFF' );
)->store()
if grep { $credit_type eq $_ } ( 'PAYMENT', 'WRITEOFF' );
Koha::Plugins->call(
'after_account_action',

View File

@ -28,6 +28,9 @@ use Koha::Database;
use Koha::DateUtils qw( dt_from_string );
use Koha::Exceptions::Account;
use Koha::Patron::Debarments;
use Koha::Notice::Templates;
use C4::Letters;
use C4::Scrubber;
use base qw(Koha::Object Koha::Object::Mixin::AdditionalFields);
@ -848,15 +851,32 @@ sub adjust {
# Catch cases that require patron refunds
if ( $new_outstanding < 0 ) {
my $account = Koha::Patrons->find( $self->borrowernumber )->account;
my $credit = $account->add_credit(
{
amount => $new_outstanding * -1,
type => 'OVERPAYMENT',
interface => $interface,
( $update_type eq 'overdue_update' ? ( item_id => $self->itemnumber ) : () ),
}
);
# Only generate an OVERPAYMENT credit for reversible payments
# (e.g. cash payments). Non-reversible credits such as writeoffs,
# discounts and cancellations should simply be absorbed when the
# fine is adjusted downward - they must not produce a refund
# credit for the patron.
my $total_non_reversible = 0;
if ( my $debit_offsets = $self->debit_offsets ) {
my $nr_offsets = $debit_offsets->filter_by_non_reversible;
$total_non_reversible += abs( $_->amount ) for $nr_offsets->as_list;
}
my $total_applied = $amount_before - $amount_outstanding_before;
my $total_reversible = $total_applied - $total_non_reversible;
my $overpayment_amount = $total_reversible - $amount;
if ( $overpayment_amount > 0 ) {
my $account = Koha::Patrons->find( $self->borrowernumber )->account;
$account->add_credit(
{
amount => $overpayment_amount,
type => 'OVERPAYMENT',
interface => $interface,
( $update_type eq 'overdue_update' ? ( item_id => $self->itemnumber ) : () ),
}
);
}
$new_outstanding = 0;
}
@ -1097,6 +1117,48 @@ sub store {
}
}
#if there is a matching debit description letter, pull that content and render it for the accountline.description
if ( $self->is_debit && !$self->in_storage ) {
my $debit_type = Koha::Account::DebitTypes->find( $self->debit_type_code );
if ($debit_type) {
# get the patron's prederred lang
my $patron = $self->patron;
my $lang = $patron ? $patron->lang : 'default';
# check to make sure the custom notice exists
my $notice_exists = Koha::Notice::Templates->search(
{
module => 'debit_description',
code => $debit_type->code,
message_transport_type => 'email',
lang => $lang,
}
)->count;
if ($notice_exists) {
my $letter = C4::Letters::GetPreparedLetter(
module => 'debit_description',
letter_code => $debit_type->code,
message_transport_type => 'email',
lang => $lang,
tables => {
borrowers => $self->borrowernumber,
branches => $self->branchcode,
},
substitute => {
accountline => $self,
},
);
if ( $letter && $letter->{content} ) {
my $scrubber = C4::Scrubber->new('note');
$self->description( $scrubber->scrub( $letter->{content} ) );
}
}
}
}
return $self->SUPER::store();
}

View File

@ -309,6 +309,7 @@ sub to_api_mapping {
return {
basketno => 'basket_id',
basketname => 'name',
note => 'internal_note',
booksellernote => 'vendor_note',
contractnumber => 'contract_id',
creationdate => 'creation_date',

View File

@ -25,7 +25,7 @@ use Koha::Subscriptions;
use C4::Contract qw( GetContracts );
use base qw( Koha::Object );
use base qw( Koha::Object::Mixin::AdditionalFields Koha::Object );
=head1 NAME
@ -46,8 +46,7 @@ Returns the list of baskets for the vendor
sub baskets {
my ($self) = @_;
my $baskets_rs = $self->_result->aqbaskets;
return Koha::Acquisition::Baskets->_new_from_dbic($baskets_rs);
return Koha::Acquisition::Baskets->_new_from_dbic( scalar $self->_result->baskets );
}
=head3 contacts
@ -112,8 +111,7 @@ Returns the list of subscriptions for the vendor
sub subscriptions {
my ($self) = @_;
my $rs = $self->_result->subscriptions;
return Koha::Subscriptions->_new_from_dbic($rs);
return Koha::Subscriptions->_new_from_dbic( scalar $self->_result->subscriptions );
}
=head3 aliases
@ -198,8 +196,7 @@ Returns the list of invoices for the vendor
sub invoices {
my ($self) = @_;
my $invoices_rs = $self->_result->aqinvoices;
return Koha::Acquisition::Invoices->_new_from_dbic($invoices_rs);
return Koha::Acquisition::Invoices->_new_from_dbic( scalar $self->_result->invoices );
}
=head3 to_api_mapping

View File

@ -19,7 +19,7 @@ use Modern::Perl;
use Koha::Acquisition::Bookseller;
use base qw( Koha::Objects );
use base qw( Koha::Object::Mixin::AdditionalFields Koha::Objects );
=head1 NAME

View File

@ -654,6 +654,7 @@ sub to_api_mapping {
parent_ordernumber => 'parent_order_id',
quantityreceived => 'quantity_received',
replacementprice => 'replacement_price',
servicing_instruction => undef, # EDIFACT related
sort1 => 'statistics_1',
sort1_authcat => 'statistics_1_authcat',
sort2 => 'statistics_2',

View File

@ -160,6 +160,7 @@ on this order (checking quantity and quantityreceived).
sub filter_by_active {
my ($self) = @_;
my $source_alias = $self->_resultset->current_source_alias;
return $self->search(
{
'-or' => [
@ -169,7 +170,7 @@ sub filter_by_active {
},
{ 'orderstatus' => [ 'ordered', 'partial' ] }
],
quantityreceived => { '<', \['COALESCE(me.quantity,0)'] },
quantityreceived => { '<', \["COALESCE($source_alias.quantity,0)"] },
},
{ join => 'basket' }
);

View File

@ -19,8 +19,15 @@ package Koha::ActionLog;
use Modern::Perl;
use JSON qw( decode_json );
use C4::Context;
# Avoid `use Koha::Patrons` here: Koha::ActionLog is loaded transitively by
# C4::Log very early in the dependency chain, before Koha::Patron has been
# fully defined. The accessors below reach for Koha::Patron (and Koha::Items)
# at call time, by which point they are reliably available.
use base qw(Koha::Object);
=head1 NAME
@ -31,8 +38,76 @@ Koha::ActionLog - Koha ActionLog Object class
=head2 Class methods
=head3 librarian
my $librarian = $log->librarian;
Returns the related I<Koha::Patron> object for the librarian who performed
the action, or I<undef> if the C<user> column was not set (e.g. cron jobs).
=cut
sub librarian {
my ($self) = @_;
my $rs = $self->_result->librarian;
return unless $rs;
return Koha::Patron->_new_from_dbic($rs);
}
=head3 patron
my $patron = $log->patron;
Returns the related I<Koha::Patron> object joined on the C<object> column.
Only meaningful for rows where C<object> is a borrowernumber (MEMBERS,
CIRCULATION, FINES and APIKEYS modules); for other modules the join may
match an unrelated patron and the caller must filter by C<module>.
=cut
sub patron {
my ($self) = @_;
my $rs = $self->_result->patron;
return unless $rs;
return Koha::Patron->_new_from_dbic($rs);
}
=head3 item
my $item = $log->item;
Returns the related I<Koha::Item> for a CIRCULATION log entry, or I<undef>.
The itemnumber is not stored in a dedicated column; it lives inside the
C<info> field, which for circulation is either a JSON payload carrying an
C<itemnumber> key (ISSUE, RENEWAL) or a bare itemnumber string (RETURN).
Returns I<undef> for non-circulation rows, when no itemnumber can be
derived, or when the item has since been deleted.
=cut
sub item {
my ($self) = @_;
return unless ( $self->module // q{} ) eq 'CIRCULATION';
my $info = $self->info;
return unless defined $info;
my $itemnumber;
my $decoded = eval { decode_json($info) };
if ( ref($decoded) eq 'HASH' && $decoded->{itemnumber} ) {
$itemnumber = $decoded->{itemnumber};
} elsif ( $info =~ /^\s*(\d+)\s*$/ ) {
$itemnumber = $1;
}
return unless $itemnumber;
require Koha::Items;
return Koha::Items->find($itemnumber);
}
=head2 Internal methods
=head3 _type

View File

@ -95,17 +95,32 @@ sub search_for_display {
my ( $self, $params ) = @_;
my $lang = $params->{lang} || q{};
# If lang is not default, we will search for entries matching $lang but fallback to default if $lang is not found
# Then we need a subquery count in where clause; DBIx::Class/SQL::Abstract does not support it, fallback to literal SQL
my $subquery =
qq|(SELECT COUNT(*) FROM additional_contents_localizations WHERE lang='$lang' AND additional_content_id=me.additional_content_id)=0|;
my $search_params = get_public_query_search_params($params);
$search_params->{lang} = 'default' if !$lang || $lang eq 'default';
$search_params->{-or} = [ { 'lang' => $lang }, '-and' => [ 'lang', 'default', \$subquery ] ]
if !$search_params->{lang};
my $attribs = { prefetch => 'additional_content', order_by => 'additional_content.number' };
unless ( $search_params->{lang} ) {
$search_params->{-or} = [
{ lang => $lang },
{
-and => [
{ lang => 'default' },
\[
'(SELECT COUNT(*) FROM additional_contents_localizations WHERE lang = ? AND additional_content_id = me.additional_content_id) = 0',
$lang
]
]
}
];
}
my $attribs = {
prefetch => 'additional_content',
order_by => [
\['ISNULL(additional_content.number)'],
{ -asc => 'additional_content.number' },
{ -desc => 'additional_content.published_on' },
],
};
return Koha::AdditionalContentsLocalizations->search( $search_params, $attribs );
}

View File

@ -55,14 +55,17 @@ sub to_api {
my ( $self, $params ) = @_;
my $table_to_resource = {
'accountlines:credit' => 'credit',
'accountlines:debit' => 'debit',
'aqbasket' => 'basket',
'aqinvoices' => 'invoice',
'erm_licenses' => 'license',
'erm_agreements' => 'agreement',
'erm_packages' => 'package',
'aqorders' => 'order',
'accountlines:credit' => 'credit',
'accountlines:debit' => 'debit',
'aqbasket' => 'basket',
'aqinvoices' => 'invoice',
'erm_licenses' => 'license',
'erm_agreements' => 'agreement',
'erm_packages' => 'package',
'aqorders' => 'order',
'aqbooksellers:vendor' => 'vendor',
'erm_titles' => 'title',
'erm_agreement_periods' => 'agreement_period'
};
my $json = $self->SUPER::to_api($params);

View File

@ -28,6 +28,23 @@ sub field {
return Koha::AdditionalField->_new_from_dbic( $self->_result()->field() );
}
=head3 to_api
Overloaded to_api method to exclude internal fields from API representation
=cut
sub to_api {
my ( $self, $params ) = @_;
my $json = $self->SUPER::to_api($params);
# Remove internal database optimization fields that shouldn't be exposed via API
delete $json->{record_table};
return $json;
}
=head2 Internal methods
=head3 _type

View File

@ -19,7 +19,10 @@ package Koha::ApiKey;
use Modern::Perl;
use Clone qw( clone );
use C4::Log qw( logaction );
use Koha::AuthUtils qw(hash_password);
use Koha::Exceptions::ApiKey;
use Koha::Exceptions::Object;
use List::MoreUtils qw(any);
@ -44,9 +47,12 @@ Overloaded I<store> method.
=cut
sub store {
my ($self) = @_;
my ( $self, $params ) = @_;
$params //= {};
if ( $self->in_storage ) {
my $is_new = !$self->in_storage;
if ( !$is_new ) {
my %dirty_columns = $self->_result->get_dirty_columns;
# only allow 'description' and 'active' to be updated
@ -60,11 +66,27 @@ sub store {
{
secret => Koha::AuthUtils::hash_password( $self->{_plain_text_secret} ),
client_id => $self->_generate_unused_uuid('client_id'),
active => 1,
}
);
}
return $self->SUPER::store();
my $log = !$params->{skip_log} && C4::Context->preference('ApiKeyLog');
my $original;
# Log the action unless explicitly skipped
$original = $is_new ? undef : $self->get_from_storage()
if $log;
$self->SUPER::store();
if ( $log && $is_new ) {
logaction( 'APIKEYS', 'CREATE', $self->patron_id, $self, undef );
} elsif ($log) {
logaction( 'APIKEYS', 'MODIFY', $self->patron_id, $self, undef, $original );
}
return $self;
}
=head3 validate_secret
@ -103,6 +125,80 @@ sub plain_text_secret {
return;
}
=head3 delete
$api_key->delete;
Overloaded delete method to add logging.
=cut
sub delete {
my ($self) = @_;
my $patron_id = $self->patron_id;
my $result = $self->SUPER::delete();
logaction( 'APIKEYS', 'DELETE', $patron_id )
if C4::Context->preference('ApiKeyLog');
return $result;
}
=head3 revoke
$api_key->revoke;
Revokes the API key by setting active to 0.
Throws an exception if the key is already revoked.
=cut
sub revoke {
my ($self) = @_;
Koha::Exceptions::ApiKey::AlreadyRevoked->throw
if !$self->active;
my $log = C4::Context->preference('ApiKeyLog');
my $original = $log ? clone( $self->unblessed ) : undef;
$self->active(0);
$self->store( { skip_log => 1 } );
logaction( 'APIKEYS', 'REVOKE', $self->patron_id, $self, undef, $original )
if $log;
return $self;
}
=head3 activate
$api_key->activate;
Activates the API key by setting active to 1.
Throws an exception if the key is already active.
=cut
sub activate {
my ($self) = @_;
Koha::Exceptions::ApiKey::AlreadyActive->throw
if $self->active;
my $log = C4::Context->preference('ApiKeyLog');
my $original = $log ? clone( $self->unblessed ) : undef;
$self->active(1);
$self->store( { skip_log => 1 } );
logaction( 'APIKEYS', 'ACTIVATE', $self->patron_id, $self, undef, $original )
if $log;
return $self;
}
=head2 Internal methods
=cut

View File

@ -80,9 +80,10 @@ sub _render_script {
return $c->reply->not_found;
}
my $sub = CGI::Compile->compile($script);
my $app = CGI::Emulate::PSGI->handler($sub);
my $response = $app->( $c->_psgi_env() );
my $abs_script = $c->app->home->rel_file($script);
my $sub = CGI::Compile->compile($abs_script);
my $app = CGI::Emulate::PSGI->handler($sub);
my $response = $app->( $c->_psgi_env() );
$c->res->code( $response->[0] );
$c->res->headers->from_hash( { @{ $response->[1] } } );

View File

@ -73,18 +73,27 @@ sub register {
my $method = $c->req->method;
if ( $method eq 'GET' || $method eq 'HEAD' || $method eq 'OPTIONS' || $method eq 'TRACE' ) {
my $op = $c->req->param('op');
my $op = $c->req->param('op');
my $login_op = $c->req->param('login_op');
if ( $op && $op =~ /^cud-/ ) {
return $c->reply->exception(
'Incorrect use of a safe HTTP method with an `op` parameter that starts with "cud-"')
->rendered(400);
} elsif ( defined $login_op ) {
return $c->reply->exception('Incorrect use of a safe HTTP method with a `login_op` parameter')
->rendered(400);
}
} else {
my $op = $c->req->param('op');
if ( $op && $op !~ /^cud-/ ) {
my $op = $c->req->param('op');
my $login_op = $c->req->param('login_op');
if ( ( $op && $op !~ /^cud-/ ) && !$login_op ) {
return $c->reply->exception(
'Incorrect use of an unsafe HTTP method with an `op` parameter that does not start with "cud-"')
->rendered(400);
} elsif ( defined $login_op && $login_op !~ /^cud-login$/ ) {
return $c->reply->exception(
'Incorrect use of an unsafe HTTP method with a `login_op` parameter that does not contain cud-login'
)->rendered(400);
}
if ( $c->cookie('CGISESSID') && !$self->is_csrf_valid( $c->req ) ) {

View File

@ -72,13 +72,7 @@ sub _get_data_and_patron {
if defined $claim->{$pkey};
}
my $value = $mapped_data->{$matchpoint};
my $matchpoint_rs = Koha::Patrons->search( { $matchpoint => $value } );
if ( defined $value and $matchpoint_rs->count ) {
$patron = $matchpoint_rs->next;
}
$patron = $self->_find_patron_by_matchpoint( $matchpoint, $mapped_data->{$matchpoint} );
}
if ( defined $config->{userinfo_url} ) {
@ -88,10 +82,7 @@ sub _get_data_and_patron {
my $code = $tx->res->code || 'No response';
return if $code ne '200';
my $claim =
$tx->res->headers->content_type =~ m!^(application/json|text/javascript)(;\s*charset=\S+)?$!
? $tx->res->json
: Mojo::Parameters->new( $tx->res->body )->to_hash;
my $claim = $tx->res->json // Mojo::Parameters->new( $tx->res->body )->to_hash;
foreach my $key ( keys %$mapping ) {
my $pkey = $mapping->{$key};
@ -101,13 +92,7 @@ sub _get_data_and_patron {
}
unless ($patron) {
my $value = $mapped_data->{$matchpoint};
my $matchpoint_rs = Koha::Patrons->search( { $matchpoint => $value } );
if ( defined $value and $matchpoint_rs->count ) {
$patron = $matchpoint_rs->next;
}
$patron = $self->_find_patron_by_matchpoint( $matchpoint, $mapped_data->{$matchpoint} );
}
}
@ -115,4 +100,23 @@ sub _get_data_and_patron {
return ( $mapped_data, $patron );
}
=head3 _find_patron_by_matchpoint
my $patron = $client->_find_patron_by_matchpoint( $matchpoint, $value );
Internal method to find a patron by the given matchpoint and value.
Returns the patron object if found, undef otherwise.
=cut
sub _find_patron_by_matchpoint {
my ( $self, $matchpoint, $value ) = @_;
return unless defined $value;
my $matchpoint_rs = Koha::Patrons->search( { $matchpoint => $value } );
return $matchpoint_rs->count ? $matchpoint_rs->next : undef;
}
1;

View File

@ -19,14 +19,10 @@ package Koha::Auth::Identity::Provider;
use Modern::Perl;
use base qw(Koha::Object);
use JSON qw( decode_json encode_json );
use Try::Tiny;
use base qw(Koha::Object Koha::Object::JSONFields);
use Koha::Auth::Identity::Provider::Domains;
use Koha::Exceptions;
use Koha::Exceptions::Object;
=head1 NAME
@ -61,11 +57,7 @@ Returns a I<hashref> containing the configuration parameters for the provider.
sub get_config {
my ($self) = @_;
return try {
return decode_json( Encode::encode_utf8( $self->config ) );
} catch {
Koha::Exceptions::Object::BadValue->throw("Error reading JSON data: $_");
};
return $self->decode_json_field( { field => 'config' } );
}
=head3 set_config
@ -104,14 +96,7 @@ sub set_config {
}
}
try {
my $encoded_config = encode_json($config);
$self->config($encoded_config);
} catch {
Koha::Exceptions::Object::BadValue->throw("Error serializing data into JSON: $_");
};
return $self;
return $self->set_encoded_json_field( { data => $config, field => 'config' } );
}
=head3 get_mapping
@ -125,11 +110,7 @@ Returns a I<hashref> containing the attribute mapping for the provider.
sub get_mapping {
my ($self) = @_;
return try {
return decode_json( Encode::encode_utf8( $self->mapping ) );
} catch {
Koha::Exceptions::Object::BadValue->throw("Error reading JSON data: $_");
};
return $self->decode_json_field( { field => 'mapping' } );
}
=head3 set_mapping
@ -143,14 +124,7 @@ This method stores the passed mappings in JSON format.
sub set_mapping {
my ( $self, $mapping ) = @_;
try {
my $encoded_mapping = encode_json($mapping);
$self->mapping($encoded_mapping);
} catch {
Koha::Exceptions::Object::BadValue->throw("Error serializing data into JSON: $_");
};
return $self;
return $self->set_encoded_json_field( { data => $mapping, field => 'mapping' } );
}
=head3 upgrade_class

View File

@ -19,12 +19,17 @@ package Koha::Authority;
use Modern::Perl;
use Scalar::Util qw( blessed );
use C4::Context;
use base qw(Koha::Object);
use Koha::Authority::ControlledIndicators;
use Koha::SearchEngine::Search;
use C4::Heading qw( new_from_field );
use C4::Charset qw( StripNonXmlChars );
=head1 NAME
@ -289,6 +294,112 @@ sub record_schema {
: 'MARC21';
}
=head3 $MARC21_THESAURUS_TO_CONTROL_FIELD_008_11
my $code = $Koha::Authority::MARC21_THESAURUS_TO_CONTROL_FIELD_008_11->{$thesaurus};
The mapping of a subject heading's thesaurus name (as derived by
C4::Heading from MARC indicator 2 / subfield $2, e.g. 'lcsh', 'mesh', or a
raw $2 code such as 'fast') to its MARC21 authority 008/11 code ("Subject
heading system/thesaurus"), per
L<https://www.loc.gov/marc/authority/ad008.html>.
Note that C<sears> and C<aat> map to 008/11 values here but never appear
as a thesaurus name derived by C4::Heading, because they don't have their
own MARC21 indicator 2 value (see the LOC list linked above).
=cut
our $MARC21_THESAURUS_TO_CONTROL_FIELD_008_11 = {
lcsh => 'a',
lcac => 'b',
mesh => 'c',
nal => 'd',
notapplicable => 'n',
cash => 'k',
rvm => 'v',
aat => 'r',
sears => 's',
notdefined => 'z',
notspecified => '|',
};
# The subset of $MARC21_THESAURUS_TO_CONTROL_FIELD_008_11 keys that
# C4::Heading uses to mean "no specific thesaurus could be identified from
# the source heading's indicators" (a blank/unrecognised indicator 2, or
# indicator 2 = 4), rather than a genuine, named thesaurus. Kept private -
# callers should go through has_known_thesaurus() rather than consulting
# this directly.
my $UNDEFINED_THESAURUS_VALUES = {
notdefined => 1,
notspecified => 1,
};
=head3 has_known_thesaurus
if ( Koha::Authority->has_known_thesaurus($thesaurus) ) { ... }
Returns true if C<$thesaurus> (as derived by C4::Heading from MARC
indicator 2 / subfield $2, e.g. 'lcsh', 'mesh', or a raw $2 code such as
'fast') identifies an actual thesaurus. Returns false if C<$thesaurus> is
undefined, or is one of the placeholder values C4::Heading returns when
no thesaurus could be identified from the source heading's indicators
('notdefined', 'notspecified').
Callers that want to code a new authority's 008/11 (and 040$f) from a
heading's thesaurus should check this first, and leave the site's own
default (I<MARCAuthorityControlField008>) in place otherwise.
=cut
sub has_known_thesaurus {
my ( $class, $thesaurus ) = @_;
return 0 unless defined $thesaurus;
return $UNDEFINED_THESAURUS_VALUES->{$thesaurus} ? 0 : 1;
}
=head3 default_marc21_008
my $default_008 = Koha::Authority->default_marc21_008;
my $default_008 = Koha::Authority->default_marc21_008($thesaurus);
Returns the default 34-character body (i.e. everything after the
6-character date) to use for a new MARC21 authority record's 008 field,
honouring the I<MARCAuthorityControlField008> system preference and
falling back to Koha's hardcoded default when it isn't set to a valid (at
least 34 character) value.
If C<$thesaurus> is passed, position 11 ("Subject heading
system/thesaurus") is set from
L<$MARC21_THESAURUS_TO_CONTROL_FIELD_008_11> instead of whatever the
default/syspref value carries, falling back to 'z' (Other) for a
thesaurus with no dedicated code -- consistent with how Elasticsearch
indexing already reconciles 008/11 vs 040$f (see
Koha::SearchEngine::Elasticsearch::_process_mappings, where a 'z' is
dropped in favour of a genuine 040$f value).
=cut
sub default_marc21_008 {
my ( $class, $thesaurus ) = @_;
my $default_008 = C4::Context->preference('MARCAuthorityControlField008');
if ( !$default_008 or length($default_008) < 34 ) {
$default_008 = '|| aca||aabn | a|a d';
} else {
$default_008 = substr( $default_008, 0, 34 );
}
if ( defined $thesaurus ) {
# Position 11 is absolute (over date + body); within the 34-char body alone that's index 11 - 6 = 5.
substr( $default_008, 5, 1, $MARC21_THESAURUS_TO_CONTROL_FIELD_008_11->{$thesaurus} // 'z' );
}
return $default_008;
}
=head3 to_api_mapping
This method returns the mapping for representing a Koha::Authority object
@ -324,16 +435,89 @@ sub move_to_deleted {
delete $data->{modification_time}; # trigger new timestamp
# Set leader 05 (deleted)
my $format = C4::Context->preference('marcflavour') eq 'UNIMARC' ? 'UNIMARCAUTH' : 'MARC21';
my $record = $self->record;
$record->leader( substr( $record->leader, 0, 5 ) . 'd' . substr( $record->leader, 6, 18 ) );
$data->{marcxml} = $record->as_xml_record($format);
my $format = C4::Context->preference('marcflavour') eq 'UNIMARC' ? 'UNIMARCAUTH' : 'MARC21';
my $record = eval { $self->record };
my $invalid_record = $@ || !$record;
if ($invalid_record) {
$record = $self->record_strip_nonxml;
}
# We don't do this for biblios, and deleted authorities aren't accessible outside the DB
# not in OAI, export, REST, etc. so we should just use the marcxml if we cannot update it
# i.e. it is more important to allow the deletion than to force this
$record->leader( substr( $record->leader, 0, 5 ) . 'd' . substr( $record->leader, 6, 18 ) ) if $record;
$data->{marcxml} = $record ? $record->as_xml_record($format) : $self->marcxml;
return Koha::Database->new->schema->resultset('DeletedauthHeader')->create($data);
}
=head3 record_strip_nonxml
my $record = $authority->record_strip_nonxml;
This subroutine is intended for cases where we encounter a record that cannot be parsed, but want
to make a good effort to present the record (for harvesting, deletion, editing) rather than throwing
an exception
Will return undef if the record cannot be built
=cut
sub record_strip_nonxml {
my ( $self, $params ) = @_;
$params //= {};
my $record;
my $marcxml_error;
my $flavour = $self->record_schema;
eval {
$record = MARC::Record->new_from_xml(
StripNonXmlChars( $self->marcxml ), 'UTF-8',
$flavour
);
};
if ($@) {
$marcxml_error = $@;
chomp $marcxml_error;
warn $marcxml_error;
return;
}
return $record;
}
=head2 Internal methods
=head3 _unblessed_for_log
Returns a plain hashref of authority column values that are independent of
the attached MARC record, for use in action log diffing.
Columns derived from MARC (C<heading>, C<authtrees>), the raw C<marcxml>
blob, and system-managed timestamps (C<datecreated>, C<modification_time>)
are omitted because they either duplicate MARC data already captured in
the C<_marc> key of the log entry or add no diff signal. Only columns
carrying independent Koha metadata are retained: C<authtypecode>,
C<origincode>, and C<linkid>.
=cut
# auth_header columns that exist independently of the MARC record.
my @_LOG_FIELDS = qw( authtypecode origincode linkid );
sub _unblessed_for_log {
my ($self) = @_;
my $full = $self->unblessed;
my %data;
for my $key (@_LOG_FIELDS) {
next unless exists $full->{$key} && defined $full->{$key};
$data{$key} = blessed( $full->{$key} ) ? "$full->{$key}" : $full->{$key};
}
return \%data;
}
=head3 _type
=cut

View File

@ -121,10 +121,12 @@ sub enqueue {
my $job_type = $self->job_type;
my $job_size = $params->{job_size};
my $job_args = $params->{job_args};
my $job_context = $params->{job_context} // C4::Context->userenv;
my $job_context = $params->{job_context} // C4::Context->userenv // {};
my $job_queue = $params->{job_queue} // 'default';
my $json = $self->json;
$job_context = {%$job_context};
# session_id must not be logged
delete $job_context->{session_id};
@ -189,12 +191,6 @@ sub process {
return {} if ref($self) ne 'Koha::BackgroundJob';
# Our background jobs are called in forked processes
# to ensure we have all plugin hooks and data we call
# get_enabled_plugins at the star of processing
# to populate the cache
Koha::Plugins->get_enabled_plugins();
my $derived_class = $self->_derived_class;
$args ||= {};
@ -459,6 +455,7 @@ sub core_types_to_classes {
marc_import_revert_batch => 'Koha::BackgroundJob::MARCImportRevertBatch',
pseudonymize_statistic => 'Koha::BackgroundJob::PseudonymizeStatistic',
import_from_kbart_file => 'Koha::BackgroundJob::ImportKBARTFile',
patron_import => 'Koha::BackgroundJob::PatronImport',
file_transport_test => 'Koha::BackgroundJob::TestTransport',
};
}
@ -475,7 +472,6 @@ sub plugin_types_to_classes {
my ($self) = @_;
unless ( exists $self->{_plugin_mapping} ) {
require Koha::Plugins;
my @plugins = Koha::Plugins->new()->GetPlugins( { method => 'background_tasks', } );
foreach my $plugin (@plugins) {

View File

@ -70,6 +70,7 @@ sub process {
my $use_transport_cost_matrix = C4::Context->preference("UseTransportCostMatrix");
my $transport_cost_matrix = $use_transport_cost_matrix ? TransportCostMatrix() : undef;
my $branches_to_use = load_branches_to_pull_from($use_transport_cost_matrix);
my $unallocated = C4::Context->preference('RealTimeHoldsQueueUnallocated');
my @messages;
@ -82,8 +83,9 @@ sub process {
{
biblio_id => $biblio_id,
branches_to_use => $branches_to_use,
delete => 1,
transport_cost_matrix => $transport_cost_matrix
delete => $unallocated ? 0 : 1,
transport_cost_matrix => $transport_cost_matrix,
unallocated => $unallocated,
}
);
push @messages,

View File

@ -104,7 +104,7 @@ sub process {
};
try {
my ($results) = Koha::Items->search( { itemnumber => \@record_ids } )->batch_update(
my ($results) = Koha::Items->search( { itemnumber => { -in => \@record_ids } } )->batch_update(
{
regex_mod => $regex_mod,
new_values => $new_values,

View File

@ -84,11 +84,10 @@ sub process {
progress_callback => sub { my $job_progress = shift; $self->progress($job_progress)->store },
}
);
my $count = $num_added + $num_updated;
if ($count) {
$self->set( { progress => $count, size => $count } );
} else { # TODO Refine later
$self->set( { progress => 0, status => 'failed' } );
my $count = $num_added + $num_updated + $num_ignored;
$self->set( { progress => $count } );
if ( $count != $size ) {
$self->set( { status => 'failed' } );
}
} catch {
warn $_;

View File

@ -0,0 +1,138 @@
package Koha::BackgroundJob::PatronImport;
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use Try::Tiny qw( catch try );
use Koha::List::Patron qw( AddPatronList AddPatronsToList );
use Koha::Patrons::Import;
use Koha::UploadedFiles;
use base 'Koha::BackgroundJob';
=head1 NAME
Koha::BackgroundJob::PatronImport - Import patrons from a CSV file as a background job
This is a subclass of Koha::BackgroundJob.
=head1 API
=head2 Class methods
=head3 job_type
Define the job type of this job: patron_import
=cut
sub job_type { return 'patron_import' }
=head3 process
Process the job.
=cut
sub process {
my ( $self, $args ) = @_;
return if $self->status eq 'cancelled';
$self->start;
my $upload = Koha::UploadedFiles->find( $args->{uploaded_file_id} );
unless ($upload) {
$self->status('failed')->store;
return;
}
my $fh = $upload->file_handle;
my $result = try {
Koha::Patrons::Import->new->import_patrons(
{
file => $fh,
defaults => $args->{defaults},
matchpoint => $args->{matchpoint},
overwrite_cardnumber => $args->{overwrite_cardnumber},
overwrite_passwords => $args->{overwrite_passwords},
preserve_extended_attributes => $args->{preserve_extended_attributes},
preserve_fields => $args->{preserve_fields},
update_dateexpiry => $args->{update_dateexpiry},
update_dateexpiry_from_today => $args->{update_dateexpiry_from_today},
update_dateexpiry_from_existing => $args->{update_dateexpiry_from_existing},
send_welcome => $args->{send_welcome},
step_callback => sub { $self->step },
}
);
} catch {
$self->status('failed')->store;
return;
};
my $data = $self->decoded_data;
if ($result) {
if ( $args->{createpatronlist} && @{ $result->{imported_borrowers} // [] } ) {
my $list = AddPatronList( { name => $args->{patronlistname} } );
AddPatronsToList( { list => $list, borrowernumbers => $result->{imported_borrowers} } );
$data->{patron_list_name} = $args->{patronlistname};
}
$data->{feedback} = $result->{feedback};
$data->{errors} = $result->{errors};
$data->{imported} = $result->{imported};
$data->{overwritten} = $result->{overwritten};
$data->{already_in_db} = $result->{already_in_db};
$data->{invalid} = $result->{invalid};
$data->{total} = $result->{imported} + $result->{overwritten} + $result->{already_in_db} + $result->{invalid};
}
$self->finish($data);
}
=head3 enqueue
Enqueue the new job.
=cut
sub enqueue {
my ( $self, $args ) = @_;
return unless $args->{uploaded_file_id};
my $upload = Koha::UploadedFiles->find( $args->{uploaded_file_id} );
return unless $upload;
my $fh = $upload->file_handle;
my $line_count = 0;
while ( my $line = <$fh> ) {
$line_count++ if $line =~ /\S/;
}
close $fh;
$self->SUPER::enqueue(
{
job_size => $line_count > 1 ? $line_count - 1 : 1,
job_args => $args,
job_queue => 'long_tasks',
}
);
}
1;

View File

@ -18,6 +18,7 @@ package Koha::BackgroundJob::PseudonymizeStatistic;
use Modern::Perl;
use Koha::PseudonymizedTransactions;
use Koha::Statistics;
use base 'Koha::BackgroundJob';
@ -53,6 +54,8 @@ sub process {
my $statistic = $args->{statistic};
my $stat_object = Koha::Statistic->new($statistic);
Koha::PseudonymizedTransaction->create_from_statistic($stat_object);
$self->progress(1)
; #We could use step here, but we are going to save the object during finish and this saves a DB write
$self->finish( { data => "" } ); # We want to clear the job data to avoid storing patron information
}

View File

@ -53,7 +53,10 @@ sub process {
my $transport = Koha::File::Transports->find( $args->{transport_id} );
$transport->test_connection;
my $status = { status => 'ok' };
my $status = {
status => 'ok', transport_id => $transport->id, transport_name => $transport->name,
background_job_id => $self->id
};
my $messages = $transport->object_messages;
for my $message (@$messages) {
$status->{status} = 'errors' if $message->{type} eq 'error';

View File

@ -20,11 +20,12 @@ package Koha::Biblio;
use Modern::Perl;
use List::MoreUtils qw( any );
use Scalar::Util qw( blessed );
use URI;
use URI::Escape qw( uri_escape_utf8 );
use Try::Tiny;
use C4::Koha qw( GetNormalizedISBN GetNormalizedUPC GetNormalizedOCLCNumber );
use C4::Koha qw( GetNormalizedISBN );
use C4::Biblio qw( DelBiblio );
use C4::Serials qw( CountSubscriptionFromBiblionumber );
use C4::Reserves qw( MergeHolds );
@ -42,6 +43,7 @@ use Koha::Biblio::Metadatas;
use Koha::Biblio::Metadata::Extractor;
use Koha::Biblio::ItemGroups;
use Koha::Biblioitems;
use Koha::BiblioFrameworks;
use Koha::Cache::Memory::Lite;
use Koha::Bookings;
use Koha::Checkouts;
@ -329,8 +331,13 @@ sub can_be_edited {
Koha::Exceptions::MissingParameter->throw( error => "The patron parameter is missing or invalid" )
unless $patron && ref($patron) eq 'Koha::Patron';
my $is_fast_add =
defined $self->frameworkcode && $self->frameworkcode ne ''
? Koha::BiblioFrameworks->find( $self->frameworkcode )->is_fast_add
: 0;
my $editcatalogue =
$self->frameworkcode eq 'FA'
$is_fast_add
? [ 'fast_cataloging', 'edit_catalogue' ]
: 'edit_catalogue';
@ -394,10 +401,25 @@ sub check_booking {
? $existing_bookings->search( { booking_id => { '!=' => $booking_id } } )->count
: $existing_bookings->count;
# Only count checkouts on bookable items; checkouts on non-bookable
# sibling items should not reduce the pool of available bookable items.
my $bookable_item_ids = [ $bookable_items->reset->get_column('itemnumber') ];
my $checkouts = $self->current_checkouts->search(
{
date_due => { '>=' => $dtf->format_datetime($start_date) },
"me.itemnumber" => { '-not_in' => $existing_bookings->_resultset->get_column('item_id')->as_query }
date_due => { '>=' => $dtf->format_datetime($start_date) },
"me.itemnumber" => {
'-not_in' => $existing_bookings->_resultset->get_column('item_id')->as_query,
'-in' => $bookable_item_ids,
},
# A checkout linked to the booking under consideration must not
# block that booking's own updates (see Koha::Item->check_booking)
(
defined($booking_id)
? ( "me.booking_id" => [ undef, { '!=' => $booking_id } ] )
: ()
),
}
);
$booked_count += $checkouts->count;
@ -785,7 +807,7 @@ return the current holds placed on this record
sub holds {
my ( $self, $params, $attributes ) = @_;
$attributes->{order_by} = 'priority' unless exists $attributes->{order_by};
my $hold_rs = $self->_result->reserves->search( $params, $attributes );
my $hold_rs = $self->_result->holds->search( $params, $attributes );
return Koha::Holds->_new_from_dbic($hold_rs);
}
@ -1610,7 +1632,9 @@ sub get_marc_authors {
if ( $marcflavour eq "UNIMARC" ) {
$fields_filter = '200';
} else { # marc21/normarc
$fields_filter = '100';
$fields_filter = '1..';
$mintag = '100';
$maxtag = '111';
}
my @first_authors = @{
@ -1687,6 +1711,19 @@ sub normalized_upc {
return $self->metadata_extractor->get_normalized_upc;
}
=head3 normalized_ean
my $normalized_ean = $biblio->normalized_ean
Normalizes and returns the EAN found in the MARC record.
=cut
sub normalized_ean {
my ($self) = @_;
return $self->metadata_extractor->get_normalized_ean;
}
=head3 normalized_oclc
my $normalized_oclc = $biblio->normalized_oclc
@ -2138,6 +2175,51 @@ sub link_marc_host {
return $self;
}
=head3 age_restriction
my $age = $biblio->age_restriction;
Extracts the numeric age restriction from the biblio's agerestriction field
using the AgeRestrictionMarker system preference.
Returns the age (integer) or undef if no restriction is found.
=cut
sub age_restriction {
my ($self) = @_;
my $restriction_string = $self->biblioitem->agerestriction;
return unless $restriction_string;
my @values = split ' ', uc($restriction_string);
return unless @values;
my @markers = map { my $m = uc($_); $m =~ s/^\s+//; $m =~ s/\s+$//; $m }
@{ C4::Context->multivalue_preference('AgeRestrictionMarker') };
return unless @markers;
my $index = 0;
my $restriction_age = 0;
for my $value (@values) {
$index++;
for my $marker (@markers) {
if ( $marker eq $value ) {
if ( $index <= $#values ) {
$restriction_age += $values[$index];
}
last;
} elsif ( $value =~ /^\Q$marker\E(\d+)$/ ) {
$restriction_age += $1;
last;
}
}
last if ( $restriction_age > 0 );
}
return $restriction_age || undef;
}
=head3 recalls
my $recalls = $biblio->recalls;
@ -2157,12 +2239,14 @@ sub recalls {
Does biblio-level checks and returns the items attached to this biblio that are available for recall
If hold_convert $param is included, this is a check to convert a hold to a recall, so we should not check for an existing hold.
=cut
sub can_be_recalled {
my ( $self, $params ) = @_;
return 0 if !( C4::Context->preference('UseRecalls') );
return 0 if ( C4::Context->preference('UseRecalls') eq "off" );
my $patron = $params->{patron};
@ -2182,7 +2266,7 @@ sub can_be_recalled {
my @all_itemnumbers;
foreach my $item (@all_items) {
push( @all_itemnumbers, $item->itemnumber );
if ( $item->can_be_recalled( { patron => $patron } ) ) {
if ( $item->can_be_recalled( { patron => $patron, hold_convert => $params->{hold_convert} } ) ) {
push( @itemtypes, $item->effective_itemtype );
push( @itemnumbers, $item->itemnumber );
push( @items, $item );
@ -2395,7 +2479,35 @@ sub merge_with {
=head2 Internal methods
=head3 type
=head3 _unblessed_for_log
Returns a plain hashref of biblio column values that are not derived from
the attached MARC record, for use in action log diffing.
Fields such as C<author>, C<title>, C<subtitle> etc. are omitted because
they are denormalised copies of MARC data and would duplicate what is
already captured in the C<_marc> key of the log entry. Only fields that
carry independent Koha metadata are retained: C<frameworkcode>,
C<opac_suppressed>, and C<serial>. The C<timestamp> and C<datecreated>
columns are also excluded as they are system-managed and add no signal.
=cut
# Biblio table columns that exist independently of the MARC record.
my @_LOG_FIELDS = qw( frameworkcode opac_suppressed serial );
sub _unblessed_for_log {
my ($self) = @_;
my $full = $self->unblessed;
my %data;
for my $key (@_LOG_FIELDS) {
next unless exists $full->{$key} && defined $full->{$key};
$data{$key} = blessed( $full->{$key} ) ? "$full->{$key}" : $full->{$key};
}
return \%data;
}
=head3 _type
=cut

View File

@ -24,14 +24,19 @@ use C4::Biblio qw( GetMarcFromKohaField );
use C4::Charset qw( StripNonXmlChars );
use C4::Items qw( GetMarcItem );
use Koha::Biblio::Metadata::Error;
use Koha::Biblio::Metadata::Errors;
use Koha::Database;
use Koha::Exceptions::Metadata;
use Koha::Logger;
use Koha::RecordSources;
use base qw(Koha::Object);
=head1 NAME
=encoding utf-8
Koha::Metadata - Koha Metadata Object class
=head1 API
@ -40,6 +45,188 @@ Koha::Metadata - Koha Metadata Object class
=cut
=head3 store
Metadata specific store method to catch errant characters prior
to committing to the database.
If the MARCXML cannot be parsed but can be recovered, the recovered version is
saved and an error row per fault is written to C<biblio_metadata_errors> so
callers and the UI can notify the user. Two recoverable faults are currently
handled, and either or both may apply to the same record:
=over 4
=item * non-XML characters (recorded as C<nonxml_stripped>, recovered via
C<StripNonXmlChars>)
=item * datafields with no subfields at all, which C<MARC::File::XML> refuses
to parse (recorded as C<empty_datafield_stripped>, recovered by removing the
empty datafield)
=back
Any pre-existing error row is left alone when the record parses cleanly - they
are review flags requiring explicit human resolution and should not be
silently cleared by a routine re-save.
If the MARCXML cannot be recovered at all, a
I<Koha::Exceptions::Metadata::Invalid> exception is thrown.
=cut
sub store {
my $self = shift;
# Reset messages so object_messages reflects only this store call
$self->{_messages} = [];
my %recoveries; # error_type => arrayref of occurrences, for faults that were fixed
# Check marcxml will roundtrip
if ( $self->format eq 'marcxml' ) {
my $marcxml = eval {
MARC::Record::new_from_xml(
$self->metadata, 'UTF-8',
$self->schema
);
};
my $marcxml_error = $@;
chomp $marcxml_error;
unless ($marcxml) {
my ( $stripped_record, $stripped_metadata, $stripped_recoveries ) =
$self->repair_marcxml( $self->metadata, $self->schema );
if ($stripped_record) {
Koha::Logger->get->warn(
sprintf(
"Metadata for bibliographic record (biblionumber=%s) was automatically repaired before saving: %s",
$self->biblionumber // 'N/A', $marcxml_error
)
);
$self->metadata($stripped_metadata);
%recoveries = %$stripped_recoveries;
} else {
# Truly unrecoverable
Koha::Logger->get->warn($marcxml_error);
Koha::Exceptions::Metadata::Invalid->throw(
id => $self->id,
biblionumber => $self->biblionumber,
format => $self->format,
schema => $self->schema,
decoding_error => $marcxml_error,
);
}
}
}
$self->SUPER::store;
# If this save triggered a fresh repair, record each fault as a DB error row and
# signal the event via object_messages. A fault already on record for this metadata_id
# (same error_type and message) is left as-is rather than duplicated, so re-harvesting or
# re-importing a source record that is still dirty doesn't pile up repeat rows for the
# same underlying issue.
for my $error_type ( sort keys %recoveries ) {
my $occurrences = $recoveries{$error_type};
my $formatter = _error_message_formatter($error_type);
if (@$occurrences) {
for my $occ (@$occurrences) {
$self->_add_error_once( $error_type, $formatter->($occ), $occ );
}
} else {
# Fallback: couldn't pinpoint individual occurrences – store a generic message
$self->_add_error_once( $error_type, 'details unavailable' );
}
$self->add_message(
{
message => $error_type,
type => 'warning',
payload => ( join( "\n", map { $formatter->($_) } @$occurrences ) || 'details unavailable' ),
}
);
}
return $self;
}
=head3 metadata_errors
my $errors = $metadata->metadata_errors;
Returns a I<Koha::Biblio::Metadata::Errors> resultset for errors associated
with this metadata record.
=cut
sub metadata_errors {
my ($self) = @_;
return Koha::Biblio::Metadata::Errors->_new_from_dbic( scalar $self->_result->biblio_metadata_errors );
}
=head3 _add_error_once
$self->_add_error_once( $error_type, $message, $occurrence );
Records a C<biblio_metadata_errors> row for this metadata record, unless an
identical row (same C<error_type> and C<message>) already exists - so
re-triggering the same fault on a source record that stays dirty across
repeated saves doesn't pile up duplicate rows. C<$occurrence> is the optional
occurrence hashref produced by C<_find_nonxml_chars>/C<_find_empty_datafields>;
when its C<field> key looks like C<TAG$sub> or C<TAG>, the C<tag>/C<subfield>
columns are populated so callers (e.g. the cataloguing UI) don't need to parse
them back out of the free-text message.
=cut
sub _add_error_once {
my ( $self, $error_type, $message, $occurrence ) = @_;
return
if Koha::Biblio::Metadata::Errors->search(
{
metadata_id => $self->id,
error_type => $error_type,
message => $message,
}
)->count;
my ( $tag, $subfield ) =
( $occurrence && $occurrence->{field} ) ? _split_field_ref( $occurrence->{field} ) : ( undef, undef );
Koha::Biblio::Metadata::Error->new(
{
metadata_id => $self->id,
error_type => $error_type,
message => $message,
tag => $tag,
subfield => $subfield,
}
)->store;
}
=head3 _split_field_ref
my ( $tag, $subfield ) = _split_field_ref( $field_ref );
Splits a human-readable field reference such as C<245$a> into its tag and
subfield parts. Field references with no subfield (control fields, or a
datafield reported as a whole e.g. from C<_find_empty_datafields>) return an
undefined subfield.
=cut
sub _split_field_ref {
my ($field_ref) = @_;
return $field_ref =~ /^(\w+)\$(\w)$/ ? ( $1, $2 ) : ( $field_ref, undef );
}
=head3 record
my $record = $metadata->record;
@ -255,6 +442,249 @@ sub _embed_items {
return $record;
}
=head3 _context_snippet
my $snippet = _context_snippet( \@chars, $pos_0, $char_ord );
Given an array-ref of characters C<\@chars>, a 0-based position C<$pos_0> of
a bad character, and its ordinal C<$char_ord>, returns a two-line string:
=over 4
=item * Line 1 – up to 30 characters of context either side of the bad
character, with the bad character replaced by a visible glyph (Unicode
Control Pictures U+2400–U+241F for C0 controls, C<␡> for DEL, C<?>
otherwise) and ellipses when the window is truncated.
=item * Line 2 – a caret (C<^>) aligned beneath the visible glyph.
=back
Example output (4-space indent):
t $xtac arr␈\ $btxt V $2rdacontent
^
=cut
sub _context_snippet {
my ( $chars_ref, $pos_0, $char_ord ) = @_;
# Visible stand-in for the removed character:
# C0 controls (U+0000–U+001F) → Unicode Control Pictures (U+2400–U+241F)
# DEL (U+007F) → U+2421 SYMBOL FOR DELETE (␡)
# anything else → U+FFFD REPLACEMENT CHARACTER (?)
my $visible =
$char_ord <= 0x1F ? chr( $char_ord + 0x2400 )
: $char_ord == 0x7F ? "\x{2421}"
: "\x{FFFD}";
my $last = $#{$chars_ref};
my $win = 30;
my $pre_start = ( $pos_0 > $win ) ? $pos_0 - $win : 0;
my $post_end = ( $pos_0 + $win < $last ) ? $pos_0 + $win : $last;
my $pre = $pos_0 > 0 ? join( '', @{$chars_ref}[ $pre_start .. $pos_0 - 1 ] ) : '';
my $post = $pos_0 < $last ? join( '', @{$chars_ref}[ $pos_0 + 1 .. $post_end ] ) : '';
my $prefix = ( $pos_0 > $win ) ? '...' : '';
my $suffix = ( $pos_0 + $win < $last ) ? '...' : '';
my $indent = ' ';
my $line = "$indent$prefix$pre$visible$post$suffix";
my $caret_col = length($indent) + length($prefix) + length($pre);
return "$line\n" . ( ' ' x $caret_col ) . '^';
}
=head3 repair_marcxml
my ( $record, $repaired_xml, $recoveries ) =
Koha::Biblio::Metadata->repair_marcxml( $marcxml, $marc_schema );
Attempts to repair a MARCXML string that fails to parse, by stripping
whichever of the known-recoverable faults (non-XML characters,
subfield-less datafields) are present. This is the same recovery logic used
by C<store>, exposed as a class method so other callers that parse MARCXML
ahead of a C<Koha::Biblio::Metadata> save - such as C<Koha::Import::Record>
staging records - benefit from the same repair capability instead of
re-implementing a weaker version of it.
Returns, on success, the repaired C<MARC::Record>, the repaired XML string,
and a hashref of C<< error_type => \@occurrences >> (see
C<_find_nonxml_chars>/C<_find_empty_datafields>) describing what was fixed.
Returns an empty list if the XML has none of the known-recoverable faults,
or if it still doesn't parse once they're stripped.
=cut
sub repair_marcxml {
my ( $class, $marcxml, $marc_schema ) = @_;
my @nonxml_chars = _find_nonxml_chars($marcxml);
my @empty_datafields = _find_empty_datafields($marcxml);
return unless @nonxml_chars || @empty_datafields;
my $stripped_metadata = $marcxml;
$stripped_metadata = StripNonXmlChars($stripped_metadata) if @nonxml_chars;
$stripped_metadata = _strip_empty_datafields($stripped_metadata) if @empty_datafields;
my $stripped_record = eval { MARC::Record::new_from_xml( $stripped_metadata, 'UTF-8', $marc_schema ); };
return unless $stripped_record;
my %recoveries;
$recoveries{ Koha::Biblio::Metadata::Error::NONXML_STRIPPED() } = \@nonxml_chars if @nonxml_chars;
$recoveries{ Koha::Biblio::Metadata::Error::EMPTY_DATAFIELD_STRIPPED() } = \@empty_datafields if @empty_datafields;
return ( $stripped_record, $stripped_metadata, \%recoveries );
}
=head3 _find_nonxml_chars
my @occurrences = _find_nonxml_chars( $marcxml_string );
Scans a raw MARC XML string for every individual character that is illegal
in XML 1.0. Returns a list of hashrefs (one per character occurrence) with
the following keys:
=over 4
=item * C<field> - human-readable field reference, e.g. C<245$a> or C<001>
=item * C<char_ord> - ordinal (decimal) value of the bad character
=item * C<position> - 1-based character offset within the field value
=item * C<snippet> - two-line string with context window and caret pointer (see C<_context_snippet>)
=back
The same character class is used as L<C4::Charset/StripNonXmlChars>, so
every occurrence returned here is exactly one that would be stripped.
=cut
sub _find_nonxml_chars {
my ($marcxml) = @_;
# Characters illegal in XML 1.0 – identical to the set stripped by StripNonXmlChars
my $non_xml_re = qr/[^\x09\x0A\x0D\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]/;
my @occurrences;
my $scan = sub {
my ( $field_ref, $val ) = @_;
my @chars = split //, $val;
for my $i ( 0 .. $#chars ) {
if ( $chars[$i] =~ $non_xml_re ) {
push @occurrences, {
field => $field_ref,
char_ord => ord( $chars[$i] ),
position => $i + 1,
snippet => _context_snippet( \@chars, $i, ord( $chars[$i] ) ),
};
}
}
};
# Scan every subfield inside every datafield
while ( $marcxml =~ m{<datafield\b[^>]*\btag="(\w+)"[^>]*>(.*?)</datafield>}gs ) {
my ( $tag, $content ) = ( $1, $2 );
while ( $content =~ m{<subfield\b[^>]*\bcode="(\w)"[^>]*>(.*?)</subfield>}gs ) {
$scan->( "$tag\$$1", $2 );
}
}
# Scan control fields
while ( $marcxml =~ m{<controlfield\b[^>]*\btag="(\w+)"[^>]*>(.*?)</controlfield>}gs ) {
$scan->( $1, $2 );
}
return @occurrences;
}
=head3 _find_empty_datafields
my @occurrences = _find_empty_datafields( $marcxml_string );
Scans a raw MARC XML string for C<< <datafield> >> elements with no
C<< <subfield> >> children at all. C<MARC::File::XML> refuses to parse such
a field (it dies with "Field NNN must have at least one subfield"), which
happens with legacy data saved before this validation existed.
Returns a list of hashrefs (one per occurrence) with the single key
C<field>, the datafield's tag.
=cut
sub _find_empty_datafields {
my ($marcxml) = @_;
my @occurrences;
while ( $marcxml =~ m{<datafield\b([^>]*)>\s*</datafield>|<datafield\b([^>]*)/>}gs ) {
my $attrs = defined $1 ? $1 : $2;
my ($tag) = $attrs =~ /\btag="(\w+)"/;
push @occurrences, { field => $tag // 'unknown' };
}
return @occurrences;
}
=head3 _strip_empty_datafields
my $cleaned_marcxml = _strip_empty_datafields( $marcxml_string );
Removes every C<< <datafield> >> element with no C<< <subfield> >> children
from a raw MARC XML string, so the record can be parsed and saved. The
removed field's data cannot be recovered - callers should record an error
row (see C<_find_empty_datafields>) so a cataloguer can review and re-add it.
=cut
sub _strip_empty_datafields {
my ($marcxml) = @_;
return $marcxml unless defined $marcxml;
$marcxml =~ s{<datafield\b[^>]*>\s*</datafield>}{}gs;
$marcxml =~ s{<datafield\b[^>]*/>}{}gs;
return $marcxml;
}
=head3 _error_message_formatter
my $formatter = _error_message_formatter( $error_type );
my $message = $formatter->( $occurrence );
Returns a coderef that turns one occurrence hashref (as produced by
C<_find_nonxml_chars> or C<_find_empty_datafields>) into a human-readable
message line for the given C<$error_type>.
=cut
sub _error_message_formatter {
my ($error_type) = @_;
return sub {
my ($occ) = @_;
return sprintf(
"%s: invalid char value %d (U+%04X) at position %d\n%s",
$occ->{field}, $occ->{char_ord}, $occ->{char_ord}, $occ->{position}, $occ->{snippet}
);
}
if $error_type eq Koha::Biblio::Metadata::Error::NONXML_STRIPPED;
return sub {
my ($occ) = @_;
return sprintf( "%s: field removed, it had no subfields", $occ->{field} );
}
if $error_type eq Koha::Biblio::Metadata::Error::EMPTY_DATAFIELD_STRIPPED;
return sub { 'details unavailable' };
}
=head3 _type
=cut

View File

@ -0,0 +1,70 @@
package Koha::Biblio::Metadata::Error;
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use Koha::Exceptions;
use base qw(Koha::Object);
=head1 NAME
Koha::Biblio::Metadata::Error - Koha Biblio Metadata Error object class
=head1 Constants
=cut
use constant {
NONXML_STRIPPED => 'nonxml_stripped',
EMPTY_DATAFIELD_STRIPPED => 'empty_datafield_stripped',
};
my %VALID_ERROR_TYPES = map { $_ => 1 } ( NONXML_STRIPPED, EMPTY_DATAFIELD_STRIPPED );
=head1 API
=head2 Class methods
=head3 store
Rejects an C<error_type> outside the known set, so a typo in a future caller
fails loudly at store time rather than silently writing an unrecognised value
into the database.
=cut
sub store {
my ($self) = @_;
Koha::Exceptions::BadParameter->throw( parameter => 'error_type' )
unless $VALID_ERROR_TYPES{ $self->error_type // '' };
return $self->SUPER::store;
}
=head2 Internal methods
=head3 _type
=cut
sub _type {
return 'BiblioMetadataError';
}
1;

View File

@ -0,0 +1,64 @@
package Koha::Biblio::Metadata::Errors;
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use Koha::Biblio::Metadata::Error;
use base qw(Koha::Objects);
=head1 NAME
Koha::Biblio::Metadata::Errors - Koha Biblio Metadata Errors object set class
=head1 API
=head2 Class methods
=head3 by_type
my $errors = Koha::Biblio::Metadata::Errors->search(...)->by_type('nonxml_stripped');
Filters the resultset down to a single C<error_type>. See
L<Koha::Biblio::Metadata::Error> for the known type constants.
=cut
sub by_type {
my ( $self, $error_type ) = @_;
return $self->search( { error_type => $error_type } );
}
=head2 Internal methods
=head3 _type
=cut
sub _type {
return 'BiblioMetadataError';
}
=head3 object_class
=cut
sub object_class {
return 'Koha::Biblio::Metadata::Error';
}
1;

View File

@ -104,6 +104,29 @@ sub get_control_number {
return $control_number;
}
=head3 get_normalized_oclc
my $normalized_oclc = $extractor->get_normalized_oclc();
Returns a normalized OCLC number from 035$a (field is flavor-independent).
=cut
sub get_normalized_oclc {
my ($self) = @_;
my $record = $self->metadata;
my @fields = $record->field('035');
foreach my $field (@fields) {
my $oclc = $field->subfield('a');
if ( $oclc && $oclc =~ /OCoLC/i ) {
$oclc =~ s/^\([^)]*\)\s*//;
return $oclc;
}
}
return q{};
}
=head3 _normalize_string
my $normalized_string = $self->_normalize_string($string);

View File

@ -74,26 +74,27 @@ sub get_normalized_upc {
}
}
=head2 get_normalized_oclc
=head2 get_normalized_ean
my $normalized_oclc = $extractor->get_normalized_oclc();
my $normalized_ean = $extractor->get_normalized_ean();
Returns a normalized OCLC number.
Returns a normalized EAN from 024$a with indicator 1 = 3.
=cut
sub get_normalized_oclc {
sub get_normalized_ean {
my ($self) = @_;
my $record = $self->metadata;
my @fields = $record->field('035');
my @fields = $record->field('024');
foreach my $field (@fields) {
my $oclc = $field->subfield('a');
if ( $oclc && $oclc =~ /OCoLC/ ) {
$oclc =~ s/\(OCoLC\)//;
return $oclc;
my $indicator = $field->indicator(1);
my $ean = $self->_normalize_string( $field->subfield('a') );
if ( $ean && $indicator eq '3' ) {
return $ean;
}
}
return;
}
=head2 check_fixed_length

View File

@ -72,6 +72,28 @@ sub get_normalized_upc {
}
}
=head2 get_normalized_ean
my $normalized_ean = $extractor->get_normalized_ean();
Returns a normalized EAN from 073$a.
=cut
sub get_normalized_ean {
my ($self) = @_;
my $record = $self->metadata;
my @fields = $record->field('073');
foreach my $field (@fields) {
my $ean = $self->_normalize_string( $field->subfield('a') );
if ($ean) {
return $ean;
}
}
return;
}
=head1 AUTHOR
Tomas Cohen Arazi, E<lt>tomascohen@theke.ioE<gt>

View File

@ -39,6 +39,22 @@ Koha::Booking - Koha Booking object class
=head2 Class methods
=head3 set_itemtype_filter
$booking->set_itemtype_filter($itemtype_id);
Sets a transient itemtype filter for item selection. This is used when
creating "any item" bookings to filter items by type before optimal selection.
This value is not persisted to the database.
=cut
sub set_itemtype_filter {
my ( $self, $itemtype_id ) = @_;
$self->{_itemtype_filter} = $itemtype_id;
return $self;
}
=head3 biblio
Returns the related Koha::Biblio object for this booking
@ -134,6 +150,13 @@ sub store {
$self->_result->result_source->schema->txn_do(
sub {
# Nothing changed, nothing to validate; circulation may issue
# redundant status writes (e.g. when switching an on-site
# checkout) that must not re-trigger clash detection
if ( $self->in_storage && !( () = $self->_result->get_dirty_columns ) ) {
return $self->SUPER::store;
}
if ( $self->item_id ) {
Koha::Exceptions::Object::FKConstraint->throw(
broken_fk => 'item_id',
@ -157,25 +180,33 @@ sub store {
value => $self->biblio_id,
) unless ( $self->biblio );
# Throw exception for item level booking clash
Koha::Exceptions::Booking::Clash->throw()
if $self->item_id && !$self->item->check_booking(
{
start_date => $self->start_date,
end_date => $self->end_date,
booking_id => $self->in_storage ? $self->booking_id : undef
}
);
# Skip clash detection when the status transition cannot
# introduce a new clash: terminal statuses (cancelled,
# completed) and collection (issued) leave the booked
# period untouched, and the checks can produce false
# positives (e.g. against the booking's own checkout).
unless ( $self->_skip_clash_detection ) {
# Throw exception for biblio level booking clash
Koha::Exceptions::Booking::Clash->throw()
if !$self->biblio->check_booking(
{
start_date => $self->start_date,
end_date => $self->end_date,
booking_id => $self->in_storage ? $self->booking_id : undef
}
);
# Throw exception for item level booking clash
Koha::Exceptions::Booking::Clash->throw()
if $self->item_id && !$self->item->check_booking(
{
start_date => $self->start_date,
end_date => $self->end_date,
booking_id => $self->in_storage ? $self->booking_id : undef
}
);
# Throw exception for biblio level booking clash
Koha::Exceptions::Booking::Clash->throw()
if !$self->biblio->check_booking(
{
start_date => $self->start_date,
end_date => $self->end_date,
booking_id => $self->in_storage ? $self->booking_id : undef
}
);
}
# FIXME: We should be able to combine the above two functions into one
@ -190,7 +221,6 @@ sub store {
$self->_send_notice( { notice => 'BOOKING_CONFIRMATION' } );
} else {
my %updated_columns = $self->_result->get_dirty_columns;
return $self->SUPER::store unless %updated_columns;
my $old_booking = $self->get_from_storage;
$self->SUPER::store;
@ -261,17 +291,28 @@ sub _assign_item_for_booking {
my $checkouts =
$biblio->current_checkouts->search( { date_due => { '>=' => $dtf->format_datetime($start_date) } } );
my $bookable_items = $biblio->bookable_items->search(
{
itemnumber => [
'-and' => { '-not_in' => $existing_bookings->_resultset->get_column('item_id')->as_query },
{ '-not_in' => $checkouts->_resultset->get_column('itemnumber')->as_query }
]
},
{ order_by => \'RAND()', rows => 1 }
);
# Build search conditions for bookable items
my $item_search_conditions = {
itemnumber => [
'-and' => { '-not_in' => $existing_bookings->_resultset->get_column('item_id')->as_query },
{ '-not_in' => $checkouts->_resultset->get_column('itemnumber')->as_query }
]
};
my $itemnumber = $bookable_items->single->itemnumber;
# Filter by itemtype if specified (for "any item" bookings)
if ( $self->{_itemtype_filter} ) {
$item_search_conditions->{itype} = $self->{_itemtype_filter};
}
my $bookable_items = $biblio->bookable_items->search($item_search_conditions);
# Use optimal selection instead of random selection
my $optimal_item = $self->_select_optimal_item($bookable_items);
Koha::Exceptions::Booking::Clash->throw("No available items found for booking")
unless $optimal_item;
my $itemnumber = $optimal_item->itemnumber;
return $self->item_id($itemnumber);
}
@ -309,6 +350,125 @@ sub to_api_mapping {
=head2 Internal methods
=head3 _skip_clash_detection
my $bool = $self->_skip_clash_detection;
Returns true when the booking is being transitioned to a status for which
clash detection is not meaningful: the terminal statuses C<cancelled> and
C<completed>, or C<issued> (collection). A booking being collected already
occupies the period being validated, and the item's own checkout would
register as a false clash. The transition to C<issued> only skips the
checks while the booked period and item remain unchanged, and only from an
active (C<new> or C<issued>) stored status; reactivating a terminal booking
is still validated.
=cut
sub _skip_clash_detection {
my ($self) = @_;
return 0 unless $self->in_storage;
my $updated_columns = { $self->_result->get_dirty_columns };
my $new_status = $updated_columns->{status};
return 0 unless $new_status;
return 1 if any { $_ eq $new_status } qw( cancelled completed );
if ( $new_status eq 'issued' ) {
return 0 if any { exists $updated_columns->{$_} } qw( start_date end_date item_id biblio_id );
my $stored_status = $self->get_from_storage->status;
return 1 if any { $_ eq $stored_status } qw( new issued );
}
return 0;
}
=head3 _select_optimal_item
my $item_id = $self->_select_optimal_item($available_items);
Selects the optimal item from a set of available items by choosing the item
with the longest future availability after the booking ends.
This maximizes future booking opportunities by preserving items with shorter
future availability for bookings that specifically need them.
=cut
sub _select_optimal_item {
my ( $self, $available_items ) = @_;
return unless $available_items && $available_items->count > 0;
# If only one item available, return it immediately
return $available_items->next if $available_items->count == 1;
my $check_date = dt_from_string( $self->end_date )->add( days => 1 );
my $dtf = Koha::Database->new->schema->storage->datetime_parser;
# Get item IDs from the available items
my @item_ids = map { $_->itemnumber } $available_items->as_list;
# Find the item with the latest (furthest in future) next booking start date
# Items with no future bookings will have NULL and sort first (most desirable)
my $search_conditions = {
item_id => { '-in' => \@item_ids },
start_date => { '>=' => $dtf->format_datetime($check_date) },
status => { '-not_in' => [ 'cancelled', 'completed' ] }
};
# Exclude current booking if we're editing
if ( $self->in_storage ) {
$search_conditions->{booking_id} = { '!=' => $self->booking_id };
}
# Query to find the earliest future booking for each item
my $rs = Koha::Bookings->search(
$search_conditions,
{
select => [ 'item_id', { min => 'start_date', -as => 'next_booking_start' } ],
as => [ 'item_id', 'next_booking_start' ],
group_by => ['item_id'],
}
);
# Get items that have future bookings, sorted by next booking date (desc = later is better)
my %next_booking_by_item;
while ( my $row = $rs->next ) {
$next_booking_by_item{ $row->get_column('item_id') } = $row->get_column('next_booking_start');
}
# Find the best item: items without future bookings first, then by latest next booking
my $best_item_id;
my $latest_next_booking;
foreach my $item_id (@item_ids) {
my $next_booking = $next_booking_by_item{$item_id};
# Items with no future bookings are best (infinite availability)
if ( !defined $next_booking ) {
$best_item_id = $item_id;
last;
}
# Otherwise, prefer items with later (further in future) next bookings
if ( !defined $latest_next_booking || $next_booking gt $latest_next_booking ) {
$latest_next_booking = $next_booking;
$best_item_id = $item_id;
}
}
# Return the optimal item
return Koha::Items->find($best_item_id) if $best_item_id;
# Fallback: shouldn't reach here, but return first available item
$available_items->reset;
return $available_items->next;
}
=head3 _send_notice
$self->_send_notice();

View File

@ -36,18 +36,14 @@ Koha::Bookings - Koha Booking object set class
$bookings->filter_by_active;
Will return the bookings that have not ended, were cancelled or are completed.
Returns bookings that are still active, i.e. awaiting collection (C<new>) or
currently on loan (C<issued>). Cancelled and completed bookings are excluded.
=cut
sub filter_by_active {
my ($self) = @_;
return $self->search(
{
end_date => { '>=' => \'NOW()' },
status => { '-not_in' => [ 'cancelled', 'completed' ] }
}
);
return $self->search( { status => { '-in' => [ 'new', 'issued' ] } } );
}
=head2 Internal Methods

226
Koha/CSV.pm Normal file
View File

@ -0,0 +1,226 @@
package Koha::CSV;
# Copyright 2026 Theke Solutions
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use Text::CSV_XS;
use C4::Context;
use base qw(Class::Accessor);
__PACKAGE__->mk_accessors(qw( binary formula always_quote eol sep_char ));
=head1 NAME
Koha::CSV - Wrapper around Text::CSV_XS with Koha-specific defaults
=head1 SYNOPSIS
my $csv = Koha::CSV->new();
$csv->combine(@fields);
my $line = $csv->string();
# Override defaults
my $csv = Koha::CSV->new( { sep_char => ';', always_quote => 1 } );
=head1 DESCRIPTION
This class wraps Text::CSV_XS providing consistent defaults and methods
used across Koha for CSV generation and parsing.
Configuration is inherited from Koha system preferences where applicable,
but can be overridden per instance.
=head2 Configuration Inheritance
=over 4
=item * B<sep_char> - Defaults to I<CSVDelimiter> system preference (or ',' if not set)
=item * B<binary> - Always 1 (required for UTF-8 support)
=item * B<formula> - Always 'empty' (security: prevents formula injection)
=item * B<always_quote> - Defaults to 0, can be overridden
=item * B<eol> - Defaults to "\n", can be overridden
=back
=cut
=head2 new
my $csv = Koha::CSV->new();
my $csv = Koha::CSV->new({ sep_char => ';', always_quote => 1 });
Creates a new Koha::CSV object. Accepts optional parameters to override defaults.
Parameters:
- sep_char: CSV delimiter (defaults to CSVDelimiter system preference)
- always_quote: Whether to quote all fields (defaults to 0)
- eol: End of line character (defaults to "\n")
Note: binary and formula parameters are fixed for security and cannot be overridden.
=cut
sub new {
my ( $class, $params ) = @_;
$params //= {};
# Get delimiter from Koha configuration if not explicitly provided
my $sep_char = $params->{sep_char} // C4::Context->csv_delimiter();
my $self = $class->SUPER::new(
{
binary => 1, # Always 1 for UTF-8
formula => 'empty', # Hard-coded to 'empty' for security
always_quote => $params->{always_quote} // 0, # Overridable
eol => $params->{eol} // "\n", # Overridable
sep_char => $sep_char, # From Koha config or override
}
);
$self->{_csv} = Text::CSV_XS->new(
{
binary => $self->binary,
formula => $self->formula // 'empty', # Hard-coded catch-all of 'empty' for security
always_quote => $self->always_quote,
eol => $self->eol,
sep_char => $self->sep_char,
}
);
return $self;
}
=head2 combine
$csv->combine(@fields);
Combines fields into a CSV line. Returns true on success.
=cut
sub combine {
my ( $self, @fields ) = @_;
return $self->{_csv}->combine(@fields);
}
=head2 string
my $line = $csv->string();
Returns the combined CSV line as a string.
=cut
sub string {
my ($self) = @_;
return $self->{_csv}->string();
}
=head2 add_row
my $line = $csv->add_row(@fields);
# or
my $line = $csv->add_row(\@fields);
Convenience method that combines fields and returns the CSV line as a string.
Accepts either an array or arrayref of fields.
=cut
sub add_row {
my ( $self, @fields ) = @_;
# Handle arrayref or array
@fields = @{ $fields[0] } if @fields == 1 && ref $fields[0] eq 'ARRAY';
$self->combine(@fields) or return;
return $self->string();
}
=head2 parse
$csv->parse($line);
Parses a CSV line. Returns true on success.
=cut
sub parse {
my ( $self, $line ) = @_;
return $self->{_csv}->parse($line);
}
=head2 fields
my @fields = $csv->fields();
Returns the parsed fields from the last parse() call.
=cut
sub fields {
my ($self) = @_;
return $self->{_csv}->fields();
}
=head2 print
$csv->print($fh, \@fields);
Prints fields to a filehandle as a CSV line.
=cut
sub print {
my ( $self, $fh, $fields ) = @_;
return $self->{_csv}->print( $fh, $fields );
}
=head2 getline
my $fields = $csv->getline($fh);
Reads and parses a line from a filehandle. Returns arrayref of fields.
=cut
sub getline {
my ( $self, $fh ) = @_;
return $self->{_csv}->getline($fh);
}
=head2 error_diag
my $error = $csv->error_diag();
Returns error diagnostics from the last operation.
=cut
sub error_diag {
my ($self) = @_;
return $self->{_csv}->error_diag();
}
1;

View File

@ -45,9 +45,8 @@ use Module::Load::Conditional qw( can_load );
use Sereal::Encoder;
use Sereal::Decoder;
use C4::Context;
use Koha::Cache::Object;
use Koha::Config;
use Koha::Cache::Object;
use base qw(Class::Accessor);
@ -76,8 +75,8 @@ sub new {
# Should we continue to support MEMCACHED ENV vars?
$self->{'namespace'} ||= $ENV{MEMCACHED_NAMESPACE};
my @servers = split /,/, $ENV{MEMCACHED_SERVERS} || '';
$self->{namespace} ||= C4::Context->config('memcached_namespace') || 'koha';
@servers = split /,/, C4::Context->config('memcached_servers') // ''
$self->{namespace} ||= Koha::Config->get_instance->get('memcached_namespace') || 'koha';
@servers = split /,/, Koha::Config->get_instance->get('memcached_servers') // ''
unless @servers;
$self->{namespace} .= ":$subnamespace:";

View File

@ -16,12 +16,19 @@ package Koha::Cash::Register;
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use DateTime;
use Scalar::Util qw( looks_like_number );
use Try::Tiny qw( catch try );
use Koha::Account;
use Koha::Account::Line;
use Koha::Account::Lines;
use Koha::Account::Offset;
use Koha::Account::Offsets;
use Koha::Cash::Register::Actions;
use Koha::Cash::Register::Cashups;
use Koha::Database;
use Koha::DateUtils qw( dt_from_string );
use base qw(Koha::Object);
@ -112,19 +119,14 @@ Return a set of accountlines linked to this cash register since the last cashup
sub outstanding_accountlines {
my ( $self, $conditions, $attrs ) = @_;
my $since = $self->_result->search_related(
'cash_register_actions',
{ 'code' => 'CASHUP' },
{
order_by => { '-desc' => [ 'timestamp', 'id' ] },
rows => 1
}
);
# Find the start timestamp for the current "open" session
my $start_timestamp = $self->_get_session_start_timestamp;
my $local_conditions =
$since->count
? { 'date' => { '>' => $since->get_column('timestamp')->as_query } }
$start_timestamp
? { 'date' => { '>' => $start_timestamp } }
: {};
my $merged_conditions =
$conditions
? { %{$conditions}, %{$local_conditions} }
@ -138,6 +140,40 @@ sub outstanding_accountlines {
return Koha::Account::Lines->_new_from_dbic($rs);
}
=head3 cashup_in_progress
Check if there is currently a cashup in progress (CASHUP_START without corresponding CASHUP).
Returns the CASHUP_START action if in progress, undef otherwise.
=cut
sub cashup_in_progress {
my ($self) = @_;
my $last_start = $self->_result->search_related(
'cash_register_actions',
{ 'code' => 'CASHUP_START' },
{ order_by => { '-desc' => [ 'timestamp', 'id' ] }, rows => 1 }
)->single;
return unless $last_start;
my $last_completion = $self->cashups(
{},
{ order_by => { '-desc' => [ 'timestamp', 'id' ] }, rows => 1 }
)->single;
# If we have a start but no completion, or the start is more recent than completion
if ( !$last_completion
|| DateTime->compare( dt_from_string( $last_start->timestamp ), dt_from_string( $last_completion->timestamp ) )
> 0 )
{
return Koha::Cash::Register::Action->_new_from_dbic($last_start);
}
return;
}
=head3 store
Local store method to prevent direct manipulation of the 'branch_default' field
@ -204,31 +240,380 @@ sub drop_default {
return $self;
}
=head3 start_cashup
my $cashup_start = $cash_register->start_cashup(
{
manager_id => $logged_in_user->id,
}
);
Start a new cashup period. This marks the beginning of the cash counting process
and creates a snapshot point for calculating outstanding amounts. Returns the
CASHUP_START action.
=cut
sub start_cashup {
my ( $self, $params ) = @_;
# check for mandatory params
my @mandatory = ('manager_id');
for my $param (@mandatory) {
unless ( defined( $params->{$param} ) ) {
Koha::Exceptions::MissingParameter->throw( error => "The $param parameter is mandatory" );
}
}
my $manager_id = $params->{manager_id};
# Check if there's already a cashup in progress
my $last_cashup_start_rs = $self->_result->search_related(
'cash_register_actions',
{ 'code' => 'CASHUP_START' },
{ order_by => { '-desc' => [ 'timestamp', 'id' ] }, rows => 1 }
)->single;
my $last_cashup_completed = $self->cashups(
{},
{ order_by => { '-desc' => [ 'timestamp', 'id' ] }, rows => 1 }
)->single;
# If we have a CASHUP_START that's more recent than the last CASHUP, there's already an active cashup
if (
$last_cashup_start_rs
&& (
!$last_cashup_completed || DateTime->compare(
dt_from_string( $last_cashup_start_rs->timestamp ),
dt_from_string( $last_cashup_completed->timestamp )
) > 0
)
)
{
Koha::Exceptions::Object::DuplicateID->throw( error => "A cashup is already in progress for this register" );
}
my $expected_amount = $self->outstanding_accountlines->total( { payment_type => [ 'CASH', 'SIP00' ] } ) * -1;
# Prevent starting a cashup when there are no transactions at all
my $total_transactions = $self->outstanding_accountlines->total() * -1;
unless ( $total_transactions != 0 ) {
Koha::Exceptions::Object::BadValue->throw(
error => "Cannot start cashup with no transactions",
type => 'amount',
value => $total_transactions
);
}
# Create the CASHUP_START action, translating raw DBIC exceptions
my $schema = $self->_result->result_source->schema;
my $rs;
try {
$rs = $self->_result->add_to_cash_register_actions(
{
code => 'CASHUP_START',
manager_id => $manager_id,
amount => $expected_amount
}
)->discard_changes;
} catch {
$schema->translate_exception($_) if ref($_) eq 'DBIx::Class::Exception';
$_->rethrow();
};
return Koha::Cash::Register::Cashup->_new_from_dbic($rs);
}
=head3 add_cashup
my $cashup = $cash_register->add_cashup(
{
manager_id => $logged_in_user->id,
amount => $cash_register->outstanding_accountlines->total
manager_id => $logged_in_user->id,
amount => $amount_removed_from_register,
[ reconciliation_note => $reconciliation_note ]
}
);
Add a new cashup action to the till, returns the added action.
Complete a cashup period started with start_cashup(). This performs the actual
reconciliation against the amount counted and creates surplus/deficit accountlines
if needed. Returns the completed CASHUP action.
=cut
sub add_cashup {
my ( $self, $params ) = @_;
my $rs = $self->_result->add_to_cash_register_actions(
{
code => 'CASHUP',
manager_id => $params->{manager_id},
amount => $params->{amount}
# check for mandatory params
my @mandatory = ( 'manager_id', 'amount' );
for my $param (@mandatory) {
unless ( defined( $params->{$param} ) ) {
Koha::Exceptions::MissingParameter->throw( error => "The $param parameter is mandatory" );
}
)->discard_changes;
}
my $manager_id = $params->{manager_id};
return Koha::Cash::Register::Cashup->_new_from_dbic($rs);
# Validate amount is a valid number
my $amount = $params->{amount};
unless ( looks_like_number($amount) ) {
Koha::Exceptions::Account::AmountNotPositive->throw( error => 'Cashup amount must be a valid number' );
}
# Sanitize reconciliation note - treat empty/whitespace-only as undef
my $reconciliation_note = $params->{reconciliation_note};
if ( defined $reconciliation_note ) {
$reconciliation_note = substr( $reconciliation_note, 0, 1000 ); # Limit length
$reconciliation_note =~ s/^\s+|\s+$//g; # Trim whitespace
$reconciliation_note = undef if $reconciliation_note eq ''; # Empty after trim = undef
}
# Find the most recent CASHUP_START to determine if we're in two-phase mode
my $cashup_start;
my $cashup_start_rs = $self->_result->search_related(
'cash_register_actions',
{ 'code' => 'CASHUP_START' },
{ order_by => { '-desc' => [ 'timestamp', 'id' ] }, rows => 1 }
)->single;
if ($cashup_start_rs) {
# Two-phase mode: Check if this CASHUP_START has already been completed
my $existing_completion = $self->_result->search_related(
'cash_register_actions',
{
'code' => 'CASHUP',
'timestamp' => { '>' => $cashup_start_rs->timestamp }
},
{ rows => 1 }
)->single;
if ( !$existing_completion ) {
$cashup_start = Koha::Cash::Register::Cashup->_new_from_dbic($cashup_start_rs);
}
}
# Calculate expected amount from session accountlines
my $expected_amount = (
$cashup_start
? $cashup_start->accountlines->total( { payment_type => [ 'CASH', 'SIP00' ] } )
: $self->outstanding_accountlines->total( { payment_type => [ 'CASH', 'SIP00' ] } )
) * -1;
# Calculate difference (actual - expected)
my $difference = $amount - $expected_amount;
# Validate reconciliation note requirement if there's a discrepancy
if ( $difference != 0 ) {
my $note_required = C4::Context->preference('CashupReconciliationNoteRequired') // 0;
if ( $note_required && !defined $reconciliation_note ) {
Koha::Exceptions::MissingParameter->throw(
error => "Reconciliation note is required when cashup amount differs from expected amount" );
}
# If notes are restricted to an authorised value category, reject anything
# that isn't one of the allowed values (the UI only offers a matching
# select, but the API/form param isn't otherwise constrained server-side)
my $note_av_category = C4::Context->preference('CashupReconciliationNoteAuthorisedValue');
if ( $note_av_category && defined $reconciliation_note ) {
require Koha::AuthorisedValues;
my $valid = Koha::AuthorisedValues->search(
{
category => $note_av_category,
authorised_value => $reconciliation_note,
}
)->count;
unless ($valid) {
Koha::Exceptions::BadParameter->throw(
error => "Reconciliation note is not a valid authorised value for this category",
parameter => 'reconciliation_note',
);
}
}
}
# Use database transaction to ensure consistency
my $schema = $self->_result->result_source->schema;
my $cashup;
$schema->txn_do(
sub {
try {
# Create the cashup action (raw DBIC call; Koha::Object stores below are already translated)
my $rs = $self->_result->add_to_cash_register_actions(
{
code => 'CASHUP',
manager_id => $manager_id,
amount => $amount
}
)->discard_changes;
$cashup = Koha::Cash::Register::Cashup->_new_from_dbic($rs);
# Create reconciliation accountline if there's a difference
if ( $difference != 0 ) {
# Determine reconciliation date based on mode
my $reconciliation_date;
if ($cashup_start) {
# Two-phase mode: Backdate reconciliation lines to just before the CASHUP_START timestamp
# This ensures they belong to the previous session, not the current one
$reconciliation_date = \[ 'DATE_SUB(?, INTERVAL 1 SECOND)', $cashup_start->timestamp ];
} else {
# Legacy mode: Use the original backdating approach
$reconciliation_date = \'DATE_SUB(NOW(), INTERVAL 1 SECOND)';
}
if ( $difference > 0 ) {
# Surplus: more cash found than expected (credits are negative amounts)
my $surplus = Koha::Account::Line->new(
{
date => $reconciliation_date,
amount => -abs($difference), # Credits are negative
amountoutstanding => 0,
credit_type_code => 'CASHUP_SURPLUS',
manager_id => $manager_id,
interface => 'intranet',
branchcode => $self->branch,
register_id => $self->id,
payment_type => 'CASH',
note => $reconciliation_note
}
)->store();
# Record the account offset
my $account_offset = Koha::Account::Offset->new(
{
credit_id => $surplus->id,
type => 'CREATE',
amount => -abs($difference) # Offsets match the line amount
}
)->store();
} else {
# Deficit: less cash found than expected
my $deficit = Koha::Account::Line->new(
{
date => $reconciliation_date,
amount => abs($difference),
amountoutstanding => 0,
debit_type_code => 'CASHUP_DEFICIT',
manager_id => $manager_id,
interface => 'intranet',
branchcode => $self->branch,
register_id => $self->id,
payment_type => 'CASH',
note => $reconciliation_note
}
)->store();
my $account_offset = Koha::Account::Offset->new(
{
debit_id => $deficit->id,
type => 'CREATE',
amount => abs($difference) # Debits have positive offsets
}
)->store();
}
}
} catch {
$schema->translate_exception($_) if ref($_) eq 'DBIx::Class::Exception';
$_->rethrow();
};
}
);
return $cashup;
}
=head3 _get_session_start_timestamp
Internal method to determine the start timestamp for the current "open" session.
This handles the following cashup scenarios:
=over 4
=item 1. No cashups ever → undef (returns all accountlines)
=item 2. Quick cashup completed → Uses CASHUP timestamp
=item 3. Two-phase started → Uses CASHUP_START timestamp
=item 4. Two-phase completed → Uses the CASHUP_START timestamp that led to the last CASHUP
=item 5. Mixed workflows → Correctly distinguishes between quick and two-phase cashups
=back
=cut
sub _get_session_start_timestamp {
my ($self) = @_;
# Check if there's a cashup in progress (CASHUP_START without corresponding CASHUP)
my $cashup_in_progress = $self->cashup_in_progress;
if ($cashup_in_progress) {
# Scenario 3: Two-phase cashup started - return accountlines since CASHUP_START
return $cashup_in_progress->timestamp;
}
# No cashup in progress - find the most recent cashup completion
my $last_cashup = $self->cashups(
{},
{
order_by => { '-desc' => [ 'timestamp', 'id' ] },
rows => 1
}
)->single;
if ( !$last_cashup ) {
# Scenario 1: No cashups have ever taken place - return all accountlines
return;
}
# Find if this CASHUP was part of a two-phase workflow
my $corresponding_start = $self->_result->search_related(
'cash_register_actions',
{
'code' => 'CASHUP_START',
'timestamp' => { '<' => $last_cashup->timestamp }
},
{
order_by => { '-desc' => [ 'timestamp', 'id' ] },
rows => 1
}
)->single;
if ($corresponding_start) {
# Check if this CASHUP_START was completed by this CASHUP
# (no other CASHUP between them)
my $intervening_cashup = $self->_result->search_related(
'cash_register_actions',
{
'code' => 'CASHUP',
'timestamp' => {
'>' => $corresponding_start->timestamp,
'<' => $last_cashup->timestamp
}
},
{ rows => 1 }
)->single;
if ( !$intervening_cashup ) {
# Scenario 4: Two-phase cashup completed - return accountlines since the CASHUP_START
return $corresponding_start->timestamp;
}
}
# Scenarios 2 & 5: Quick cashup (or orphaned CASHUP) - return accountlines since CASHUP
return $last_cashup->timestamp;
}
=head3 to_api_mapping

View File

@ -23,7 +23,7 @@ use base qw(Koha::Cash::Register::Action);
=head1 NAME
Koha::Cash::Register::Actions - Koha Cash Register Action Object set class
Koha::Cash::Register::Cashup - Koha Cash Register Cashup Object class
=head1 API
@ -61,29 +61,43 @@ Return a hashref containing a summary of transactions that make up this cashup.
sub summary {
my ($self) = @_;
my $summary;
my $prior_cashup = Koha::Cash::Register::Cashups->search(
{
'timestamp' => { '<' => $self->timestamp },
register_id => $self->register_id
},
{
order_by => { '-desc' => [ 'timestamp', 'id' ] },
rows => 1
}
);
my $previous = $prior_cashup->single;
# Get the session boundaries for this cashup
my ( $session_start, $session_end ) = $self->_get_session_boundaries;
my $conditions =
$previous
? { 'date' => { '-between' => [ $previous->_result->get_column('timestamp'), $self->timestamp ] } }
: { 'date' => { '<' => $self->timestamp } };
my $conditions;
if ( $session_start && $session_end ) {
# Complete session: between start and end (exclusive)
$conditions = {
'date' => {
'>' => $session_start,
'<' => $session_end
}
};
} elsif ($session_end) {
# Session from beginning to end
$conditions = { 'date' => { '<' => $session_end } };
} else {
# Shouldn't happen for a completed cashup, but fallback
$conditions = { 'date' => { '<' => $self->timestamp } };
}
my $payout_transactions = $self->register->accountlines->search(
{ %{$conditions}, credit_type_code => undef },
{
%{$conditions},
credit_type_code => undef,
debit_type_code => { '!=' => 'CASHUP_DEFICIT' }
},
);
my $income_transactions = $self->register->accountlines->search(
{ %{$conditions}, debit_type_code => undef },
{
%{$conditions},
debit_type_code => undef,
credit_type_code => { '!=' => 'CASHUP_SURPLUS' }
},
);
my $income_summary = Koha::Account::Offsets->search(
@ -170,23 +184,168 @@ sub summary {
my $typed_income = $income_transactions->total( { payment_type => $type->authorised_value } );
my $typed_payout = $payout_transactions->total( { payment_type => $type->authorised_value } );
my $typed_total = ( $typed_income + $typed_payout );
push @total_grouped, { payment_type => $type->lib, total => $typed_total };
# Flip sign to match the convention used for `total` above:
# positive = net amount collected (i.e. removed from register at cashup),
# negative = net amount paid out (i.e. needed to be added to register).
push @total_grouped, { payment_type => $type->lib, total => $typed_total * -1 };
}
# Check for reconciliation lines separately (for footer display only)
my $surplus_lines =
$self->register->accountlines->search( { %{$conditions}, credit_type_code => 'CASHUP_SURPLUS' } );
my $deficit_lines =
$self->register->accountlines->search( { %{$conditions}, debit_type_code => 'CASHUP_DEFICIT' } );
my $surplus_total = $surplus_lines->count ? $surplus_lines->total : undef;
my $deficit_total = $deficit_lines->count ? $deficit_lines->total : undef;
# Extract notes from reconciliation lines
my ($surplus_record) = $surplus_lines->_resultset->search( {}, { rows => 1 } )->all;
my $surplus_note = $surplus_record ? $surplus_record->note : undef;
my ($deficit_record) = $deficit_lines->_resultset->search( {}, { rows => 1 } )->all;
my $deficit_note = $deficit_record ? $deficit_record->note : undef;
$summary = {
from_date => $previous ? $previous->timestamp : undef,
to_date => $self->timestamp,
from_date => $session_start,
to_date => $session_end,
income_grouped => \@income,
income_total => abs($income_total),
payout_grouped => \@payout,
payout_total => abs($payout_total),
total => $total * -1,
total_grouped => \@total_grouped
total_grouped => \@total_grouped,
# Reconciliation data for footer display
surplus_total => $surplus_total ? $surplus_total * 1 : undef,
deficit_total => $deficit_total ? $deficit_total * 1 : undef,
surplus_note => $surplus_note,
deficit_note => $deficit_note
};
return $summary;
}
=head3 accountlines
Fetch the accountlines associated with this cashup
=cut
sub accountlines {
my ($self) = @_;
# Get the session boundaries for this cashup
my ( $session_start, $session_end ) = $self->_get_session_boundaries;
my $conditions;
if ( $session_start && $session_end ) {
# Complete session: between start and end (exclusive)
$conditions = {
'date' => {
'>' => $session_start,
'<' => $session_end
}
};
} elsif ($session_end) {
# Session from beginning to end
$conditions = { 'date' => { '<' => $session_end } };
} else {
# Shouldn't happen for a completed cashup, but fallback
$conditions = { 'date' => { '<' => $self->timestamp } };
}
return $self->register->accountlines->search($conditions);
}
=head3 _get_session_boundaries
Internal method to determine the session boundaries for this cashup.
Returns ($session_start, $session_end) timestamps.
=cut
sub _get_session_boundaries {
my ($self) = @_;
my $session_end = $self->_get_session_end;
# Find the previous CASHUP
my $session_start;
my $previous_cashup = $self->register->cashups(
{ 'timestamp' => { '<' => $session_end } },
{
order_by => { '-desc' => [ 'timestamp', 'id' ] },
rows => 1
}
)->single;
$session_start = $previous_cashup ? $previous_cashup->_get_session_end : undef;
return ( $session_start, $session_end );
}
=head3 _get_session_end
Internal method to determine the effective end timestamp of the session
this cashup closes.
For a one-phase cashup, the session ends at C<< $self->timestamp >>.
For a two-phase workflow (a preceding C<CASHUP_START> with no intervening
C<CASHUP>), the session ends at the C<CASHUP_START> timestamp so that
transactions made between C<CASHUP_START> and C<CASHUP> are excluded
from this cashup's summary.
Returns the session end timestamp.
=cut
sub _get_session_end {
my ($self) = @_;
my $session_end = $self->timestamp;
# Find if this CASHUP was part of a two-phase workflow
my $nearest_start = $self->register->_result->search_related(
'cash_register_actions',
{
'code' => 'CASHUP_START',
'timestamp' => { '<' => $session_end }
},
{
order_by => { '-desc' => [ 'timestamp', 'id' ] },
rows => 1
}
)->single;
if ($nearest_start) {
# Check if this CASHUP_START was completed by this CASHUP
# (no other CASHUP between them)
my $intervening_cashup = $self->register->cashups(
{
'timestamp' => {
'>' => $nearest_start->timestamp,
'<' => $session_end
}
},
{ rows => 1 }
)->single;
if ( !$intervening_cashup ) {
# Two-phase workflow: session runs to CASHUP_START
$session_end = $nearest_start->timestamp;
}
}
return $session_end;
}
=head3 to_api_mapping
This method returns the mapping for representing a Koha::Cash::Register::Cashup object

Some files were not shown because too many files have changed in this diff Show More