Compare commits
55 Commits
main
...
v25.11.01-
| Author | SHA1 | Date |
|---|---|---|
|
|
c70b1d1a5e | |
|
|
af6de22633 | |
|
|
b1917a70c0 | |
|
|
0b804e8f85 | |
|
|
c1d4162a8b | |
|
|
a80f3429f0 | |
|
|
2c2c04dfb2 | |
|
|
6cefe064d4 | |
|
|
695743328b | |
|
|
4d3e5f4c98 | |
|
|
f4d343bcd7 | |
|
|
32354c3370 | |
|
|
2d5e288bb9 | |
|
|
8e87a7fa5c | |
|
|
a97137a9d2 | |
|
|
c0bb19f1cd | |
|
|
eb6cefae4e | |
|
|
9b58889359 | |
|
|
51d8bbc3f5 | |
|
|
c8f869180d | |
|
|
70a09fdcc5 | |
|
|
42da679145 | |
|
|
431d7e7fd6 | |
|
|
8c5c16f4ec | |
|
|
5e2246ce53 | |
|
|
9657018b08 | |
|
|
e96aba99e6 | |
|
|
a5d9d82b6e | |
|
|
c2749c8151 | |
|
|
8e7d76c60d | |
|
|
17aa1fd2e1 | |
|
|
3ffe6d10ab | |
|
|
e8b4fa90eb | |
|
|
a244d7a6a0 | |
|
|
6948a53333 | |
|
|
20ee4fbd72 | |
|
|
0f2ad151de | |
|
|
a7d22d5533 | |
|
|
de44fac6a3 | |
|
|
338bb59ae1 | |
|
|
73d6a7c035 | |
|
|
1cd5f8ec61 | |
|
|
b75f760946 | |
|
|
2abee1cc83 | |
|
|
b6fbded69a | |
|
|
f331ff3419 | |
|
|
64f8ab25fd | |
|
|
194e3f21bd | |
|
|
315807fd8c | |
|
|
873767c6c8 | |
|
|
35dba14c1e | |
|
|
f23d71721a | |
|
|
6f3e2c1b1b | |
|
|
c55e114091 | |
|
|
19f8f483ae |
|
|
@ -131,7 +131,10 @@ 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;
|
||||
|
|
|
|||
|
|
@ -810,7 +810,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 +818,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 +916,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 +1168,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 = ?";
|
||||
|
|
|
|||
2
Koha.pm
2
Koha.pm
|
|
@ -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 = "25.11.01.000";
|
||||
|
||||
sub version {
|
||||
return $VERSION;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,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
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,10 @@ use Exception::Class (
|
|||
'Koha::Exceptions::Patron::Relationship::InvalidRelationship' => {
|
||||
isa => 'Koha::Exceptions::Patron::Relationship',
|
||||
description => 'The specified relationship is invalid',
|
||||
fields => [ 'relationship', 'no_relationship', 'invalid_guarantor' ]
|
||||
fields => [
|
||||
'relationship', 'no_relationship', 'invalid_guarantor', 'child_guarantor',
|
||||
'guarantor_cant_have_guarantors', 'guarantor'
|
||||
],
|
||||
},
|
||||
'Koha::Exceptions::Patron::Relationship::NoGuarantor' => {
|
||||
isa => 'Koha::Exceptions::Patron::Relationship',
|
||||
|
|
@ -50,6 +53,8 @@ sub full_message {
|
|||
if ( $self->no_relationship ) {
|
||||
$msg = sprintf("No relationship passed.");
|
||||
} elsif ( $self->invalid_guarantor ) {
|
||||
$msg = sprintf("Guarantee patron cannot be a guarantor.");
|
||||
} elsif ( $self->child_guarantor ) {
|
||||
$msg = sprintf("Child patron cannot be a guarantor.");
|
||||
} else {
|
||||
$msg = sprintf( "Invalid relationship passed, '%s' is not defined.", $self->relationship );
|
||||
|
|
|
|||
19
Koha/Hold.pm
19
Koha/Hold.pm
|
|
@ -695,6 +695,19 @@ sub item {
|
|||
return Koha::Item->_new_from_dbic($rs);
|
||||
}
|
||||
|
||||
=head3 item_type
|
||||
|
||||
Returns the related Koha::ItemType object for this hold
|
||||
|
||||
=cut
|
||||
|
||||
sub item_type {
|
||||
my ($self) = @_;
|
||||
my $rs = $self->_result->itemtype;
|
||||
return unless $rs;
|
||||
return Koha::ItemType->_new_from_dbic($rs);
|
||||
}
|
||||
|
||||
=head3 item_group
|
||||
|
||||
Returns the related Koha::Biblio::ItemGroup object for this Hold
|
||||
|
|
@ -1281,7 +1294,7 @@ sub to_api_mapping {
|
|||
lowestPriority => 'lowest_priority',
|
||||
suspend => 'suspended',
|
||||
suspend_until => 'suspended_until',
|
||||
itemtype => 'item_type',
|
||||
itemtype => 'item_type_id',
|
||||
item_level_hold => 'item_level',
|
||||
};
|
||||
}
|
||||
|
|
@ -1319,6 +1332,10 @@ sub strings_map {
|
|||
pickup_library_id => { str => $self->pickup_library->branchname, type => 'library' },
|
||||
};
|
||||
|
||||
if ( defined $self->itemtype ) {
|
||||
$strings->{item_type_id} = { str => $self->item_type->description, type => 'item_type' };
|
||||
}
|
||||
|
||||
if ( defined $self->cancellation_reason ) {
|
||||
my $av = Koha::AuthorisedValues->search(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ sub to_api_mapping {
|
|||
|
||||
$library->opac_info({ lang => $lang });
|
||||
|
||||
Returns additional contents block OpacLibraryInfo for $lang or 'default'.
|
||||
Returns all additional contents blocks OpacLibraryInfo by library and $lang (or 'default').
|
||||
|
||||
Note: This replaces the former branches.opac_info column.
|
||||
|
||||
|
|
@ -385,7 +385,7 @@ Note: This replaces the former branches.opac_info column.
|
|||
|
||||
sub opac_info {
|
||||
my ( $self, $params ) = @_;
|
||||
return Koha::AdditionalContents->find_best_match(
|
||||
return Koha::AdditionalContents->search_for_display(
|
||||
{
|
||||
category => 'html_customizations',
|
||||
location => 'OpacLibraryInfo',
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ our $mapping = {
|
|||
'circ/offline' => '/circulation.html#offline-circulation-in-koha',
|
||||
'circ/on-site_checkouts' => '/circulation.html#pending-on-site-checkouts',
|
||||
'circ/overdue' => '/circulation.html#overdues',
|
||||
'circ/pendingbookings' => '/circulation.html#bookings-to-collect',
|
||||
'circ/pendingreserves' => '/circulation.html#holds-to-pull',
|
||||
'circ/renew' => '/circulation.html#renewing',
|
||||
'circ/reserveratios' => '/circulation.html#hold-ratios',
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ sub new {
|
|||
my $baseURL = $repository->self_url();
|
||||
$baseURL = $+{base_url}
|
||||
if $baseURL =~ m/(?<base_url>.*)\?.*/;
|
||||
$baseURL =~ s{/$}{}; # Strip trailing slash for CGI.pm 4.68+ compatibility
|
||||
|
||||
my $self = $class->SUPER::new(
|
||||
baseURL => $baseURL,
|
||||
|
|
|
|||
|
|
@ -157,6 +157,11 @@ sub new {
|
|||
$response = $class->new( $self, %attr );
|
||||
}
|
||||
|
||||
# Strip trailing slash from requestURL for CGI.pm 4.68+ compatibility
|
||||
my $url = $response->requestURL();
|
||||
$url =~ s{/$}{};
|
||||
$response->requestURL($url);
|
||||
|
||||
$response->set_handler( XML::SAX::Writer->new( Output => *STDOUT ) );
|
||||
$response->xslt("/opac-tmpl/xslt/OAI.xslt");
|
||||
$response->generate;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use Modern::Perl;
|
|||
|
||||
use List::MoreUtils qw( any none uniq notall zip6);
|
||||
use JSON qw( to_json );
|
||||
use Scalar::Util qw(looks_like_number);
|
||||
use Unicode::Normalize qw( NFKD );
|
||||
use Try::Tiny;
|
||||
use DateTime ();
|
||||
|
|
@ -206,8 +207,6 @@ sub store {
|
|||
my $self = shift;
|
||||
my $params = @_ ? shift : {};
|
||||
|
||||
my $guarantors = $params->{guarantors} // [];
|
||||
|
||||
$self->_result->result_source->schema->txn_do(
|
||||
sub {
|
||||
if (
|
||||
|
|
@ -328,20 +327,6 @@ sub store {
|
|||
|
||||
$self->borrowernumber(undef);
|
||||
|
||||
if ( C4::Context->preference('ChildNeedsGuarantor')
|
||||
and ( $self->is_child or $self->category->can_be_guarantee )
|
||||
and $self->contactname eq ""
|
||||
and !@$guarantors )
|
||||
{
|
||||
Koha::Exceptions::Patron::Relationship::NoGuarantor->throw();
|
||||
}
|
||||
|
||||
foreach my $guarantor (@$guarantors) {
|
||||
if ( $guarantor->is_child ) {
|
||||
Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw( invalid_guarantor => 1 );
|
||||
}
|
||||
}
|
||||
|
||||
$self = $self->SUPER::store;
|
||||
|
||||
$self->add_enrolment_fee_if_needed(0);
|
||||
|
|
@ -385,23 +370,6 @@ sub store {
|
|||
|
||||
}
|
||||
|
||||
my @existing_guarantors = $self->guarantor_relationships()->guarantors->as_list;
|
||||
push @$guarantors, @existing_guarantors;
|
||||
|
||||
if ( C4::Context->preference('ChildNeedsGuarantor')
|
||||
and ( $self->is_child or $self->category->can_be_guarantee )
|
||||
and ( !defined $self->contactname || $self->contactname eq "" )
|
||||
and !@$guarantors )
|
||||
{
|
||||
Koha::Exceptions::Patron::Relationship::NoGuarantor->throw();
|
||||
}
|
||||
|
||||
foreach my $guarantor (@$guarantors) {
|
||||
if ( $guarantor->is_child ) {
|
||||
Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw( invalid_guarantor => 1 );
|
||||
}
|
||||
}
|
||||
|
||||
# Actionlogs
|
||||
if ( C4::Context->preference("BorrowersLog") || C4::Context->preference("CardnumberLog") ) {
|
||||
my $info;
|
||||
|
|
@ -2834,6 +2802,61 @@ sub _anonymize_column {
|
|||
$self->$col($val);
|
||||
}
|
||||
|
||||
=head3 can_be_guaranteed_by
|
||||
|
||||
$patron->can_be_guaranteed_by( [
|
||||
$guarantor1,
|
||||
$guarantor2,
|
||||
...
|
||||
] );
|
||||
|
||||
Given an array of guarantors, checks if they can be guarantors for this patron.
|
||||
|
||||
Throws Koha::Exceptions::Patron::Relationship::InvalidRelationship if a proposed
|
||||
guarantor cannot be a guarantor for this patron.
|
||||
|
||||
$patron->can_be_guaranteed_by();
|
||||
|
||||
Given no guarantors, checks if this patron must be guaranteed by someone. Read
|
||||
"can be guaranteed by nobody".
|
||||
|
||||
Throws Koha::Exceptions::Patron::Relationship::NoGuarantor if this patron must
|
||||
have a guarantor.
|
||||
|
||||
=cut
|
||||
|
||||
sub can_be_guaranteed_by {
|
||||
my ( $self, $guarantors ) = @_;
|
||||
|
||||
my $category = $self->category;
|
||||
|
||||
if ( C4::Context->preference('ChildNeedsGuarantor')
|
||||
and ( $category->category_type eq 'C' or $category->can_be_guarantee )
|
||||
and scalar @$guarantors == 0 )
|
||||
{
|
||||
Koha::Exceptions::Patron::Relationship::NoGuarantor->throw();
|
||||
}
|
||||
|
||||
if ( $self->is_guarantor && scalar @$guarantors > 0 ) {
|
||||
Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw( guarantor_cant_have_guarantors => 1 );
|
||||
}
|
||||
|
||||
foreach my $guarantor (@$guarantors) {
|
||||
next if ref($guarantor) ne 'Koha::Patron';
|
||||
|
||||
if ( $guarantor->is_guarantee ) {
|
||||
Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw(
|
||||
invalid_guarantor => 1,
|
||||
guarantor => $guarantor,
|
||||
);
|
||||
} elsif ( $guarantor->is_child ) {
|
||||
Koha::Exceptions::Patron::Relationship::InvalidRelationship->throw( child_guarantor => 1 );
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head3 add_guarantor
|
||||
|
||||
my $relationship = $patron->add_guarantor(
|
||||
|
|
@ -3519,7 +3542,7 @@ sub is_patron_inside_charge_limits {
|
|||
my $guarantors_non_issues_charges = 0;
|
||||
|
||||
# Check the debt of this patrons guarantees
|
||||
if ( defined $no_issues_charge_guarantees ) {
|
||||
if ( defined $no_issues_charge_guarantees && looks_like_number($no_issues_charge_guarantees) ) {
|
||||
my @guarantees = map { $_->guarantee } $patron->guarantee_relationships->as_list;
|
||||
foreach my $g (@guarantees) {
|
||||
$guarantees_non_issues_charges += $g->account->non_issues_charges;
|
||||
|
|
@ -3527,7 +3550,9 @@ sub is_patron_inside_charge_limits {
|
|||
}
|
||||
|
||||
# Check the debt of this patrons guarantors *and* the guarantees of those guarantors
|
||||
if ( defined $no_issues_charge_guarantors_with_guarantees ) {
|
||||
if ( defined $no_issues_charge_guarantors_with_guarantees
|
||||
&& looks_like_number($no_issues_charge_guarantors_with_guarantees) )
|
||||
{
|
||||
$guarantors_non_issues_charges = $patron->relationships_debt(
|
||||
{ include_guarantors => 1, only_this_guarantor => 0, include_this_patron => 1 } );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -377,9 +377,6 @@ LINE: while ( my $borrowerline = <$handle> ) {
|
|||
$patron_attributes =
|
||||
$patron->extended_attributes->merge_and_replace_with($patron_attributes);
|
||||
}
|
||||
|
||||
# We do not want to filter by branch, maybe we should?
|
||||
Koha::Patrons->find($borrowernumber)->extended_attributes->delete;
|
||||
$patron->extended_attributes($patron_attributes);
|
||||
}
|
||||
$overwritten++;
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ sub add {
|
|||
my $pickup_library_id = $body->{pickup_library_id};
|
||||
my $item_id = $body->{item_id};
|
||||
my $patron_id = $body->{patron_id};
|
||||
my $item_type = $body->{item_type};
|
||||
my $item_type_id = $body->{item_type_id};
|
||||
my $expiration_date = $body->{expiration_date};
|
||||
my $notes = $body->{notes};
|
||||
my $hold_date = $body->{hold_date};
|
||||
|
|
@ -209,7 +209,7 @@ sub add {
|
|||
title => $biblio->title,
|
||||
itemnumber => $item_id,
|
||||
found => undef, # TODO: Why not?
|
||||
itemtype => $item_type,
|
||||
itemtype => $item_type_id,
|
||||
non_priority => $non_priority,
|
||||
item_group_id => $item_group_id,
|
||||
confirmations => $confirmations,
|
||||
|
|
|
|||
|
|
@ -122,6 +122,9 @@ sub add {
|
|||
|
||||
my $patron = Koha::Patron->new_from_api($body)->store;
|
||||
|
||||
$patron->extended_attributes(
|
||||
[ map { { code => $_->{type}, attribute => $_->{value} } } @$extended_attributes ] );
|
||||
|
||||
my $overrides = $c->stash('koha.overrides');
|
||||
if ( $overrides->{welcome_yes}
|
||||
|| ( C4::Context->preference("AutoEmailNewUser") && !$overrides->{welcome_no} ) )
|
||||
|
|
@ -157,8 +160,6 @@ sub add {
|
|||
}
|
||||
}
|
||||
|
||||
$patron->extended_attributes(
|
||||
[ map { { code => $_->{type}, attribute => $_->{value} } } @$extended_attributes ] );
|
||||
if ( C4::Context->preference('EnhancedMessagingPreferences') ) {
|
||||
C4::Members::Messaging::SetMessagingPreferencesFromDefaults(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ use base qw(Koha::Object);
|
|||
# TODO Koha::Report->store should check this before saving
|
||||
|
||||
use constant FORBIDDEN_COLUMN_MATCHES => qw(password token uuid secret);
|
||||
use constant WHITELISTED_COLUMN_NAMES =>
|
||||
qw(password_expiration_date password_expiry_days reset_password change_password min_password_length require_strong_password password_expiration_date);
|
||||
use constant ALLOWLISTED_COLUMN_NAMES =>
|
||||
qw(password_expiration_date password_expiry_days reset_password change_password min_password_length require_strong_password password_expiration_date force_password_reset_when_set_by_staff);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
|
|
@ -99,8 +99,8 @@ sub check_columns {
|
|||
@columns = grep { /$regex/i } @columns;
|
||||
}
|
||||
|
||||
# Check for whitelisted variants
|
||||
$regex = '(^' . ( join '|', WHITELISTED_COLUMN_NAMES ) . ')$'; # should be full match
|
||||
# Check for allowlisted variants
|
||||
$regex = '(^' . ( join '|', ALLOWLISTED_COLUMN_NAMES ) . ')$'; # should be full match
|
||||
@columns = grep { !/$regex/i } @columns;
|
||||
return @columns;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,24 @@ use Clone qw( clone );
|
|||
use Modern::Perl;
|
||||
use Readonly qw( Readonly );
|
||||
use Search::Elasticsearch;
|
||||
use Search::Elasticsearch::Role::Cxn;
|
||||
{
|
||||
# Monkey-patch Search::Elasticsearch::Role::Cxn::process_response
|
||||
no warnings 'redefine';
|
||||
|
||||
my $orig_sub = \&Search::Elasticsearch::Role::Cxn::process_response;
|
||||
|
||||
# Override method
|
||||
*Search::Elasticsearch::Role::Cxn::process_response = sub {
|
||||
my ( $self, $params, $code, $msg, $body, $headers ) = @_;
|
||||
|
||||
unless ( $headers && $headers->{'x-elastic-product'} && $headers->{'x-elastic-product'} eq 'Elasticsearch' ) {
|
||||
$Search::Elasticsearch::Role::Cxn::PRODUCT_CHECK_VALUE = '';
|
||||
}
|
||||
$orig_sub->(@_);
|
||||
};
|
||||
}
|
||||
|
||||
use Try::Tiny qw( catch try );
|
||||
use YAML::XS;
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ properties:
|
|||
non_priority:
|
||||
description: Set this hold as non priority
|
||||
type: boolean
|
||||
item_type:
|
||||
item_type_id:
|
||||
type:
|
||||
- string
|
||||
- "null"
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@
|
|||
type:
|
||||
- string
|
||||
- "null"
|
||||
item_type:
|
||||
item_type_id:
|
||||
description: Limit hold on one itemtype (ignored for item-level holds)
|
||||
type:
|
||||
- string
|
||||
|
|
|
|||
|
|
@ -263,7 +263,8 @@ $template->param( advancedsearchesloop => $advancedsearchesloop );
|
|||
|
||||
$template->param( searchid => scalar $cgi->param('searchid'), );
|
||||
|
||||
my $default_sort_by = C4::Context->default_catalog_sort_by;
|
||||
my $default_sort_by = C4::Context->default_catalog_sort_by;
|
||||
my @sanitized_operators = grep { $_ ne 'cud-login' } $cgi->multi_param('op');
|
||||
|
||||
# The following should only be loaded if we're bringing up the advanced search template
|
||||
if ( $template_type eq 'advsearch' ) {
|
||||
|
|
@ -274,7 +275,7 @@ if ( $template_type eq 'advsearch' ) {
|
|||
my $expanded = $cgi->param('expanded_options');
|
||||
if ( $cgi->param('edit_search') ) {
|
||||
@operands = $cgi->multi_param('q');
|
||||
@operators = $cgi->multi_param('op');
|
||||
@operators = @sanitized_operators;
|
||||
@indexes = $cgi->multi_param('idx');
|
||||
$template->param(
|
||||
sort => scalar $cgi->param('sort_by'),
|
||||
|
|
@ -370,7 +371,7 @@ $template->param( 'sort_by' => $sort_by[0] );
|
|||
|
||||
# operators include boolean and proximity operators and are used
|
||||
# to evaluate multiple operands
|
||||
my @operators = map uri_unescape($_), $cgi->multi_param('op');
|
||||
my @operators = map uri_unescape($_), @sanitized_operators;
|
||||
|
||||
# indexes are query qualifiers, like 'title', 'author', etc. They
|
||||
# can be single or multiple parameters separated by comma: kw,right-Truncation
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ return {
|
|||
my ($args) = @_;
|
||||
my ( $dbh, $out ) = @$args{qw(dbh out)};
|
||||
|
||||
say $dbh->do(
|
||||
$dbh->do(
|
||||
q{
|
||||
UPDATE columns_settings
|
||||
SET columnname = SUBSTRING(columnname,LOCATE("_",columnname)+1)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ return {
|
|||
say $out "Added new permission 'merge_borrowers'";
|
||||
|
||||
$dbh->do(
|
||||
q{INSERT INTO user_permissions (borrowernumber, module_bit, code) SELECT borrowernumber, module_bit, 'merge_borrowers' FROM user_permissions where module_bit = 4 and code = 'edit_borrowers';}
|
||||
q{INSERT IGNORE INTO user_permissions (borrowernumber, module_bit, code) SELECT borrowernumber, module_bit, 'merge_borrowers' FROM user_permissions where module_bit = 4 and code = 'edit_borrowers';}
|
||||
);
|
||||
|
||||
say $out "Added 'merge_borrowers' permission to existing users with 'edit_borrowers'";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
use Modern::Perl;
|
||||
|
||||
return {
|
||||
bug_number => undef,
|
||||
description => 'Koha 25.11.01 release',
|
||||
up => sub { },
|
||||
};
|
||||
|
|
@ -204,7 +204,7 @@
|
|||
[% CASE 'PATRON_CANT' %]
|
||||
<span>Max renewals reached</span>
|
||||
[% CASE 'TOO_MANY' %]
|
||||
<span>Too many checkout</span>
|
||||
<span>Too many checkouts</span>
|
||||
[% CASE 'PREVISSUE' %]
|
||||
<span>Patron was notified of previous issues</span>
|
||||
[% CASE 'NOT_FOR_LOAN' %]
|
||||
|
|
|
|||
|
|
@ -114,10 +114,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH basket IN supplier.loop_basket %]
|
||||
[% IF ( basket.uncertainprices ) %]
|
||||
[% SET tr_class="problem" %]
|
||||
[% END %]
|
||||
<tr class="[% tr_class | html %]">
|
||||
<tr [% IF ( basket.uncertainprices ) %]class="problem"[% END %]>
|
||||
<td>[% basket.basketno | html %]</td>
|
||||
<td><a href="/cgi-bin/koha/acqui/basket.pl?basketno=[% basket.basketno | uri %]">[% basket.basketname | html %]</a></td>
|
||||
<td data-order="[% basket.total_items | html %]">
|
||||
|
|
|
|||
|
|
@ -29,6 +29,18 @@
|
|||
[% INCLUDE 'doc-head-close.inc' %]
|
||||
[% Asset.css("lib/codemirror/codemirror.min.css") | $raw %]
|
||||
[% Asset.css("lib/codemirror/lint.min.css") | $raw %]
|
||||
[% FILTER collapse %]
|
||||
<style>
|
||||
.opac-library-info {
|
||||
border: 1px solid #dedede;
|
||||
border-radius: 2px;
|
||||
padding: 0.375rem 0.75rem;
|
||||
}
|
||||
.opac-library-info + .opac-library-info {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
[% END %]
|
||||
</head>
|
||||
|
||||
<body id="admin_branches" class="admin">
|
||||
|
|
@ -330,10 +342,20 @@
|
|||
<div class="fg-input">
|
||||
[% IF OpacLibraryInfo %]
|
||||
<!-- opac_info -->
|
||||
<div>[% OpacLibraryInfo.content | $raw %]</div>
|
||||
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&id=[% OpacLibraryInfo.id | $raw %]&editmode=wysiwyg" target="_blank">Edit HTML content</a>
|
||||
[% FOREACH info IN OpacLibraryInfo %]
|
||||
[% IF library.branchcode == info.branchcode %]
|
||||
<div id="opac_library_info_[% info.additional_content_id | $raw %]" class="opac-library-info">[% info.content | $raw %]</div>
|
||||
[% IF ( CAN_user_tools_edit_additional_contents ) %]
|
||||
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&id=[% info.additional_content_id | $raw %]&editmode=wysiwyg" target="_blank">
|
||||
<i class="fa fa-pencil"></i> Edit HTML content
|
||||
</a>
|
||||
[% END %]
|
||||
[% END %]
|
||||
[% END %]
|
||||
[% ELSE %]
|
||||
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&editmode=wysiwyg" target="_blank">Add HTML content</a>
|
||||
[% IF ( CAN_user_tools_edit_additional_contents ) %]
|
||||
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&editmode=wysiwyg" target="_blank"> <i class="fa fa-plus"></i> Add HTML content </a>
|
||||
[% END %]
|
||||
[% END %]
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -741,7 +763,23 @@
|
|||
<div class="row page-section">
|
||||
<div class="col-sm-12">
|
||||
<h2>OPAC information</h2>
|
||||
<div>[% OpacLibraryInfo.content | $raw %]</div>
|
||||
[% IF OpacLibraryInfo %]
|
||||
<!-- opac_info -->
|
||||
[% FOREACH info IN OpacLibraryInfo %]
|
||||
[% IF library.branchcode == info.branchcode %]
|
||||
<div id="opac_library_info_[% info.additional_content_id | $raw %]" class="opac-library-info">[% info.content | $raw %]</div>
|
||||
[% IF ( CAN_user_tools_edit_additional_contents ) %]
|
||||
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&id=[% info.additional_content_id | $raw %]&editmode=wysiwyg" target="_blank">
|
||||
<i class="fa fa-pencil"></i> Edit HTML content
|
||||
</a>
|
||||
[% END %]
|
||||
[% END %]
|
||||
[% END %]
|
||||
[% ELSE %]
|
||||
[% IF ( CAN_user_tools_edit_additional_contents ) %]
|
||||
<a class="btn btn-link" href="/cgi-bin/koha/tools/additional-contents.pl?category=html_customizations&op=add_form&editmode=wysiwyg" target="_blank"> <i class="fa fa-plus"></i> Add HTML content </a>
|
||||
[% END %]
|
||||
[% END %]
|
||||
</div>
|
||||
</div>
|
||||
[% END %]
|
||||
|
|
|
|||
|
|
@ -97,9 +97,9 @@
|
|||
[% END %]
|
||||
</h1>
|
||||
|
||||
<!-- Basic Information -->
|
||||
<!-- Basic information -->
|
||||
<fieldset class="rows">
|
||||
<legend>Basic Information</legend>
|
||||
<legend>Basic information</legend>
|
||||
<ol>
|
||||
<li>
|
||||
<label for="vendor_id">Vendor: </label>
|
||||
|
|
@ -170,9 +170,9 @@
|
|||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<!-- Transport Settings -->
|
||||
<!-- Transport settings -->
|
||||
<fieldset class="rows">
|
||||
<legend>Transport Settings</legend>
|
||||
<legend>Transport settings</legend>
|
||||
<ol>
|
||||
<li>
|
||||
<label for="file_transport_id">File transport: </label>
|
||||
|
|
@ -191,9 +191,9 @@
|
|||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<!-- Message Types -->
|
||||
<!-- Message types -->
|
||||
<fieldset class="rows">
|
||||
<legend>Message Types</legend>
|
||||
<legend>Message types</legend>
|
||||
<ol>
|
||||
<li>
|
||||
<label for="quotes_enabled">Quotes enabled: </label>
|
||||
|
|
@ -230,9 +230,9 @@
|
|||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<!-- Functional Switches -->
|
||||
<!-- Functional switches -->
|
||||
<fieldset class="rows">
|
||||
<legend>Functional Switches</legend>
|
||||
<legend>Functional switches</legend>
|
||||
<ol>
|
||||
<li>
|
||||
<label for="auto_orders">Automatic ordering: </label>
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ Staff interface:
|
|||
choices:
|
||||
1: Do
|
||||
0: "Don't"
|
||||
- add a filter to the location column on staff interface search results to filter items by the logged in user branch.
|
||||
- add a filter to the location column on staff interface search results to filter items by the library the user is currently logged into.
|
||||
-
|
||||
- pref: viewMARC
|
||||
choices:
|
||||
|
|
@ -216,4 +216,4 @@ Staff interface:
|
|||
"enforced": Enforce
|
||||
"enabled": Enable
|
||||
"disabled": "Don't enable"
|
||||
- two-factor authentication (2FA) for staff members.
|
||||
- two-factor authentication (2FA) for staff members.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
[% INCLUDE 'messages.inc' %]
|
||||
<h1>Items required for bookings between <span id="from_date">[% from | $KohaDates %]</span> and <span id="to_date">[% to | $KohaDates %]</span></h1>
|
||||
<h2>Reported on [% todaysdate | $KohaDates %]</h2>
|
||||
<p>The following items have not been collected for bookings. Please retrieve them and check them in.</p>
|
||||
<p>The following items have not been collected for bookings.</p>
|
||||
<div id="searchresults">
|
||||
<table id="bookingst"></table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -763,7 +763,7 @@
|
|||
<input type="hidden" name="bn-[% inputloo.counter | html %]" value="[% inputloo.borrowernumber | html %]" />
|
||||
<input type="hidden" name="nr-[% inputloo.counter | html %]" value="[% inputloo.not_returned | html %]" />
|
||||
[% END %]
|
||||
<button type="submit" data-bs-dismiss="modal" class="btn btn-default deny" accesskey="i"> <i class="fa fa-times"></i> Ignore (I) </button>
|
||||
<button type="submit" data-bs-dismiss="modal" class="btn btn-default ignore deny" accesskey="i"> <i class="fa fa-times"></i> Ignore (I) </button>
|
||||
</form>
|
||||
|
||||
<fieldset class="action">
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
[% USE To %]
|
||||
[% USE raw %]
|
||||
[% USE Asset %]
|
||||
[% USE KohaDates %]
|
||||
|
|
@ -158,6 +159,29 @@
|
|||
[% INCLUDE 'str/members-menu.inc' %]
|
||||
[% Asset.js("js/members-menu.js") | $raw %]
|
||||
[% INCLUDE 'js-biblio-format.inc' %]
|
||||
<script>
|
||||
const all_libraries = [% To.json(Branches.all) | $raw %].map(e => {
|
||||
e["_id"] = e["branchcode"];
|
||||
e["_str"] = e["branchname"];
|
||||
return e;
|
||||
});
|
||||
|
||||
const all_item_types = [% To.json(ItemTypes.Get) | $raw %].map(e => {
|
||||
e["_id"] = e["itemtype"];
|
||||
e["_str"] = e["translated_description"];
|
||||
return e;
|
||||
});
|
||||
|
||||
var coded_values = {
|
||||
library: new Map(all_libraries.map( l => [l.branchname, l.branchcode] )),
|
||||
item_type: new Map(all_item_types.map( i => [i.translated_description, i.itemtype] )),
|
||||
};
|
||||
|
||||
let filters_options = {
|
||||
item_types: all_item_types,
|
||||
libraries: all_libraries,
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var table_settings = [% TablesSettings.GetTableSettings('members', 'holdshistory', 'holdshistory-table', 'json') | $raw %];
|
||||
|
|
@ -165,6 +189,7 @@
|
|||
//Remove item type column settings
|
||||
table_settings['columns'] = table_settings['columns'].filter(function(c){return c['columnname'] != 'itemtype';});
|
||||
[% END %]
|
||||
|
||||
let current_holds_table = build_holds_table("#table_holdshistory");
|
||||
let old_holds_table = build_holds_table("#table_oldholdshistory", 1);
|
||||
function build_holds_table(table_id, old){
|
||||
|
|
@ -253,11 +278,13 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
data: "pickup_library_id:pickup_library.name",
|
||||
data: "me.pickup_library_id",
|
||||
datatype: "coded_value:library",
|
||||
dataFilter: "libraries",
|
||||
searchable: true,
|
||||
orderable: true,
|
||||
render: function (data, type, row, meta) {
|
||||
return row.pickup_library.name;
|
||||
return escape_str(row._strings.pickup_library_id ? row._strings.pickup_library_id.str : row.pickup_library_id);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -298,12 +325,14 @@
|
|||
},
|
||||
[% IF show_itemtype_column %]
|
||||
{
|
||||
data: "item_type.item_type",
|
||||
data: "item_type_id",
|
||||
datatype: "coded_value:item_type",
|
||||
dataFilter: "item_types",
|
||||
searchable: true,
|
||||
orderable: true,
|
||||
render: function (data, type, row, meta) {
|
||||
if ( row.item_type ) {
|
||||
return row.item_type.item_type;
|
||||
if ( row.item_type_id ) {
|
||||
return row._strings.item_type_id ? row._strings.item_type_id.str : row.item_type_id;
|
||||
} else {
|
||||
return _("Any item type");
|
||||
}
|
||||
|
|
@ -343,7 +372,12 @@
|
|||
}
|
||||
},
|
||||
],
|
||||
}, table_settings, true, additional_filters);
|
||||
},
|
||||
table_settings,
|
||||
true,
|
||||
additional_filters,
|
||||
filters_options,
|
||||
);
|
||||
}
|
||||
$("#current_holds_filters :checkbox, #old_holds_filters :checkbox").on("change", function(e){
|
||||
e.preventDefault();
|
||||
|
|
|
|||
|
|
@ -195,7 +195,16 @@
|
|||
<li id="ERROR_child_is_guarantor">Child patron cannot be a guarantor.</li>
|
||||
[% END %]
|
||||
[% IF ( ERROR_guarantor_is_guarantee ) %]
|
||||
<li id="ERROR_guarantor_is_guarantee">A guarantor cannot be a guarantee.</li>
|
||||
<li id="ERROR_guarantor_is_guarantee">
|
||||
Cannot add a guarantor. The proposed guarantor
|
||||
<a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% guarantor_is_guarantee.borrowernumber | uri %]" target="blank">
|
||||
[% guarantor_is_guarantee.firstname | html %] [% guarantor_is_guarantee.surname | html %] ([% guarantor_is_guarantee.cardnumber | html %])
|
||||
</a>
|
||||
is a guarantee of someone else, and guarantees cannot be guarantors.</li
|
||||
>
|
||||
[% END %]
|
||||
[% IF ( ERROR_guarantor_cant_have_guarantors ) %]
|
||||
<li id="ERROR_guarantor_cant_have_guarantors">Cannot add a guarantor. This patron is already a guarantor, and guarantors cannot have guarantors.</li>
|
||||
[% END %]
|
||||
[% IF ( ERROR_cannot_delete_guarantor ) %]
|
||||
<li id="ERROR_cannot_delete_guarantor">Cannot delete guarantor(s). A child patron needs a guarantor.</li>
|
||||
|
|
@ -959,10 +968,13 @@
|
|||
</optgroup>
|
||||
[% END %]
|
||||
</select>
|
||||
[% IF limited_category %]
|
||||
<span title="The patron's current category ([% patron_category.description | html %]) is limited to other libraries"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i></span>
|
||||
[% END %]
|
||||
<span class="required">Required</span>
|
||||
[% IF limited_category %]
|
||||
<div class="hint">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
The patron's current category ([% patron_category.description | html %]) is limited to other libraries.
|
||||
</div>
|
||||
[% END %]
|
||||
</li>
|
||||
|
||||
[% UNLESS nosort1 %]
|
||||
|
|
@ -1663,18 +1675,18 @@
|
|||
|
||||
<!-- Expiration date modal-->
|
||||
<div id="expirationDateModal" class="modal" tabindex="-1" role="dialog" aria-labelledby="expirationDateModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="expirationDateModalLabel">Confirm expiration date</h4>
|
||||
<h1 class="modal-title" id="expirationDateModalLabel">Confirm expiration date</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Change expiration date to default for this category?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="expirationDateConfirmBtn" class="btn btn-primary"><i class="fa fa-check" aria-hidden="true"></i> Yes</button>
|
||||
<button id="expirationDateCancelBtn" data-dismiss="modal" class="btn btn-secondary"><i class="fa fa-times" aria-hidden="true"></i> No</button>
|
||||
<button id="expirationDateCancelBtn" data-bs-dismiss="modal" class="btn btn-secondary"><i class="fa fa-times" aria-hidden="true"></i> No</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -423,8 +423,12 @@
|
|||
|
||||
// Handle blur and Enter key events
|
||||
input.addEventListener('blur', updateCell);
|
||||
input.addEventListener('keyup', function(e) {
|
||||
if (e.key === 'Enter') updateCell();
|
||||
input.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
updateCell();
|
||||
}
|
||||
});
|
||||
|
||||
function updateCell() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
[% USE raw %]
|
||||
[% USE Koha %]
|
||||
[% USE Branches %]
|
||||
[% USE ItemTypes %]
|
||||
[% USE KohaDates %]
|
||||
[% USE AuthorisedValues %]
|
||||
[% PROCESS 'i18n.inc' %]
|
||||
[% SET footerjs = 1 %]
|
||||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
|
|
@ -46,38 +50,51 @@
|
|||
[% IF ( do_it ) %]
|
||||
[% FOREACH mainloo IN mainloop %]
|
||||
[% IF ( mainloo.loopfilter.size ) %]
|
||||
<p><strong>Filtered on:</strong></p>
|
||||
[% FOREACH loopfilte IN mainloo.loopfilter %]
|
||||
<p>
|
||||
[% SWITCH loopfilte.crit %]
|
||||
[% CASE '0' %]
|
||||
<span>Placed on (from)</span>
|
||||
[% CASE '1' %]
|
||||
<span>Placed on (to)</span>
|
||||
[% CASE '2' %]
|
||||
<span>Received on (from)</span>
|
||||
[% CASE '3' %]
|
||||
<span>Received on (to)</span>
|
||||
[% CASE '4' %]
|
||||
<span>Vendor</span>
|
||||
[% CASE '5' %]
|
||||
<span>Home library</span>
|
||||
[% CASE '6' %]
|
||||
<span>Collection</span>
|
||||
[% CASE '7' %]
|
||||
<span>Item type</span>
|
||||
[% CASE '8' %]
|
||||
<span>Fund</span>
|
||||
[% CASE '9' %]
|
||||
<span>Sort1</span>
|
||||
[% CASE '10' %]
|
||||
<span>Sort2</span>
|
||||
[% CASE %]
|
||||
<span>Unknown filter</span>
|
||||
[% END %]
|
||||
: [% loopfilte.filter | html %]
|
||||
</p>
|
||||
[% END %]
|
||||
<h2>Filtered on:</h2>
|
||||
<ul>
|
||||
[% FOREACH loopfilte IN mainloo.loopfilter %]
|
||||
<li>
|
||||
[% SWITCH loopfilte.crit %]
|
||||
[% CASE '0' %]
|
||||
<strong>Placed on (from): </strong>
|
||||
[% loopfilte.filter | $KohaDates %]
|
||||
[% CASE '1' %]
|
||||
<strong>Placed on (to): </strong>
|
||||
[% loopfilte.filter | $KohaDates %]
|
||||
[% CASE '2' %]
|
||||
<strong>Received on (from): </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE '3' %]
|
||||
<strong>Received on (to): </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE '4' %]
|
||||
<strong>Vendor: </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE '5' %]
|
||||
<strong>Home library: </strong>
|
||||
[% Branches.GetName( loopfilte.filter ) | html %]
|
||||
[% CASE '6' %]
|
||||
<strong>Collection:</strong>
|
||||
[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => loopfilte.filter ) %]
|
||||
[% CASE '7' %]
|
||||
<strong>Item type: </strong>
|
||||
[% ItemTypes.GetDescription( loopfilte.filter ) | html %]
|
||||
[% CASE '8' %]
|
||||
<strong>Fund: </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE '9' %]
|
||||
<strong>Sort1: </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE '10' %]
|
||||
<strong>Sort2: </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE %]
|
||||
<strong>Unknown filter: </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% END %]
|
||||
</li>
|
||||
[% END %]
|
||||
</ul>
|
||||
[% END %]
|
||||
<div class="page-section">
|
||||
<table>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
[% USE Branches %]
|
||||
[% USE Koha %]
|
||||
[% USE Asset %]
|
||||
[% USE Categories %]
|
||||
[% USE KohaDates %]
|
||||
[% USE AuthorisedValues %]
|
||||
[% USE ExtendedAttributeTypes %]
|
||||
[% PROCESS 'i18n.inc' %]
|
||||
[% SET footerjs = 1 %]
|
||||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
|
|
@ -56,11 +60,75 @@
|
|||
<h1>Patrons statistics</h1>
|
||||
[% IF ( do_it ) %]
|
||||
[% FOREACH mainloo IN mainloop %]
|
||||
[% IF ( mainloo.loopfilter.size>0 ) %]
|
||||
<p>Filtered on:</p>
|
||||
[% FOREACH loopfilte IN mainloo.loopfilter %]
|
||||
<p> [% loopfilte.crit | html %] = [% loopfilte.filter | html %] </p>
|
||||
[% END %]
|
||||
[% IF ( mainloo.loopfilter.size > 0 ) %]
|
||||
<h2>Filtered on:</h2>
|
||||
<ul>
|
||||
[% FOREACH loopfilte IN mainloo.loopfilter %]
|
||||
<li>
|
||||
[% SWITCH loopfilte.crit %]
|
||||
[% CASE 'Cat code' %]
|
||||
<strong>Patron category:</strong>
|
||||
[% Categories.GetName( loopfilte.filter ) | html %]
|
||||
[% CASE 'ZIP/Postal code' %]
|
||||
<strong>ZIP/Postal code: </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE 'Branch code' %]
|
||||
<strong>Library: </strong>
|
||||
[% Branches.GetName( loopfilte.filter ) | html %]
|
||||
[% CASE 'Date of birth1' %]
|
||||
<strong>Date of birth > </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE 'Date of birth2' %]
|
||||
<strong>Date of birth < </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE 'Sex' %]
|
||||
<strong>Gender: </strong>
|
||||
[% SWITCH ( loopfilte.filter ) %]
|
||||
[% CASE 'F' %]
|
||||
<span>Female</span>
|
||||
[% CASE 'M' %]
|
||||
<span>Male</span>
|
||||
[% CASE 'O' %]
|
||||
<span>Other</span>
|
||||
[% CASE %]
|
||||
<span>None specified</span>
|
||||
[% END %]
|
||||
[% CASE 'Sort1' %]
|
||||
<strong>Patron sort1:</strong> [% AuthorisedValues.GetByCode('Bsort1', loopfilte.filter ) | html %]
|
||||
[% CASE 'Sort2' %]
|
||||
<strong>Patron sort2:</strong> [% AuthorisedValues.GetByCode('Bsort2', loopfilte.filter ) | html %]
|
||||
[% CASE 'Status' %]
|
||||
<strong>Status: </strong>
|
||||
[% SWITCH loopfilte.filter %]
|
||||
[% CASE 'debarred' %]
|
||||
<span>Restricted</span>
|
||||
[% CASE 'gonenoaddress' %]
|
||||
<span>Gone no address</span>
|
||||
[% CASE 'lost' %]
|
||||
<span>Lost card</span>
|
||||
[% CASE %]
|
||||
[% loopfilte.filter | html %]
|
||||
[% END %]
|
||||
[% CASE 'Activity' %]
|
||||
<strong>Activity: </strong>
|
||||
[% SWITCH loopfilte.filter %]
|
||||
[% CASE 'active' %]
|
||||
<span>Active</span>
|
||||
[% CASE 'nonactive' %]
|
||||
<span>Inactive</span>
|
||||
[% CASE %]
|
||||
[% loopfilte.filter | html %]
|
||||
[% END %]
|
||||
[% CASE "PA_CLASS" %]
|
||||
<strong>[% loopfilte.label | html %]:</strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE %]
|
||||
<strong>[% loopfilte.crit | html %]: </strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% END %]
|
||||
</li>
|
||||
[% END %]
|
||||
</ul>
|
||||
[% END %]
|
||||
<div class="page-section">
|
||||
<table>
|
||||
|
|
@ -227,7 +295,7 @@
|
|||
<select id="sort1" name="Filter">
|
||||
<option value="" />
|
||||
[% FOREACH SORT1_LOO IN SORT1_LOOP %]
|
||||
<option value="[% SORT1_LOO.authorized_value | html %]">[% SORT1_LOO.lib | html %]</option>
|
||||
<option value="[% SORT1_LOO.authorised_value | html %]">[% SORT1_LOO.lib | html %]</option>
|
||||
[% END %]
|
||||
</select>
|
||||
</td>
|
||||
|
|
@ -244,7 +312,7 @@
|
|||
<select id="sort2" name="Filter">
|
||||
<option value="" />
|
||||
[% FOREACH SORT2_LOO IN SORT2_LOOP %]
|
||||
<option value="[% SORT2_LOO.value | html %]">[% SORT2_LOO.value | html %]</option>
|
||||
<option value="[% SORT2_LOO.value | html %]">[% AuthorisedValues.GetByCode('Bsort2', SORT2_LOO.value ) | html %]</option>
|
||||
[% END %]
|
||||
</select>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
[% USE raw %]
|
||||
[% USE Koha %]
|
||||
[% USE Branches %]
|
||||
[% USE ItemTypes %]
|
||||
[% USE AuthorisedValues %]
|
||||
[% PROCESS 'i18n.inc' %]
|
||||
[% SET footerjs = 1 %]
|
||||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
|
|
@ -46,12 +48,52 @@
|
|||
<h1>Catalog statistics</h1>
|
||||
[% IF ( do_it ) %]
|
||||
[% FOREACH mainloo IN mainloop %]
|
||||
[% IF ( mainloo.loopfilter ) %]
|
||||
<p>Filtered on:</p>
|
||||
<h2>Filtered on:</h2>
|
||||
<ul>
|
||||
[% FOREACH loopfilte IN mainloo.loopfilter %]
|
||||
<p>[% loopfilte.crit | html %] =[% loopfilte.filter | html %]</p>
|
||||
<li>
|
||||
[% SWITCH loopfilte.crit %]
|
||||
[% CASE "Item CallNumber From" %]
|
||||
<strong>Item call number from:</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Item CallNumber To" %]
|
||||
<strong>Item call number to:</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Item type" %]
|
||||
<strong>Item type:</strong> [% ItemTypes.GetDescription( loopfilte.filter ) | html %]
|
||||
[% CASE "Publisher" %]
|
||||
<strong>Publisher:</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Publication year From" %]
|
||||
<strong>Publication year from:</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Publication year To" %]
|
||||
<strong>Publication year to:</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Library" %]
|
||||
<strong>Library:</strong> [% Branches.GetName( loopfilte.filter ) | html %]
|
||||
[% CASE "Shelving Location" %]
|
||||
<strong>Location:</strong> [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.location', authorised_value => loopfilte.filter ) %]
|
||||
[% CASE "Collection Code" %]
|
||||
<strong>Collection:</strong> [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => loopfilte.filter ) %]
|
||||
[% CASE "Status" %]
|
||||
<strong>Not for loan:</strong> [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.notforloan', authorised_value => loopfilte.filter ) %]
|
||||
[% CASE "Materials" %]
|
||||
<strong>Materials specified:</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Barcode (not like)" %]
|
||||
<strong>Barcode (not like):</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Barcode (like)" %]
|
||||
<strong>Barcode (like):</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Date acquired (item) from" %]
|
||||
<strong>Date acquired (item) from:</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Date acquired (item) to" %]
|
||||
<strong>Date acquired (item) to:</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Date deleted (item) from" %]
|
||||
<strong>Date deleted (item) from:</strong> [% loopfilte.filter | html %]
|
||||
[% CASE "Date deleted (item) to" %]
|
||||
<strong>Date deleted (item) to:</strong> [% loopfilte.filter | html %]
|
||||
[% CASE %]
|
||||
<strong>[% loopfilte.crit | html %]:</strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% END %]
|
||||
</li>
|
||||
[% END %]
|
||||
[% END %]
|
||||
</ul>
|
||||
|
||||
<div class="page-section">
|
||||
<table id="catalogue_stats">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
[% USE Branches %]
|
||||
[% USE Categories %]
|
||||
[% USE ItemTypes %]
|
||||
[% USE KohaDates %]
|
||||
[% USE AuthorisedValues %]
|
||||
[% USE ExtendedAttributeTypes %]
|
||||
[% PROCESS 'i18n.inc' %]
|
||||
[% SET footerjs = 1 %]
|
||||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
|
|
@ -55,7 +57,7 @@
|
|||
[% IF ( do_it ) %]
|
||||
[% FOREACH mainloo IN mainloop %]
|
||||
[% IF ( mainloo.loopfilter ) %]
|
||||
<p>Filtered on:</p>
|
||||
<h2>Filtered on:</h2>
|
||||
<ul>
|
||||
[% FOREACH loopfilte IN mainloo.loopfilter %]
|
||||
[% SET li_class = '' %]
|
||||
|
|
@ -80,9 +82,9 @@
|
|||
[% CASE 4 %]
|
||||
<strong>Issuing library:</strong> [% Branches.GetName( loopfilte.filter ) | html %]
|
||||
[% CASE 5 %]
|
||||
<strong>Collection:</strong> [% AuthorisedValues.GetByCode( 'CCODE', loopfilte.filter ) | html %]
|
||||
<strong>Collection:</strong> [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => loopfilte.filter ) %]
|
||||
[% CASE 6 %]
|
||||
<strong>Location:</strong> [% AuthorisedValues.GetByCode( 'LOC', loopfilte.filter ) | html %]
|
||||
<strong>Location:</strong> [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.location', authorised_value => loopfilte.filter ) %]
|
||||
[% CASE 7 %]
|
||||
<strong>Item call number > [% loopfilte.filter | html %] </strong>
|
||||
[% CASE 8 %]
|
||||
|
|
@ -127,8 +129,11 @@
|
|||
[% CASE "4" %]
|
||||
<span>Hour</span>
|
||||
[% END %]
|
||||
[% CASE "PA_CLASS" %]
|
||||
<strong>[% loopfilte.label | html %]:</strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% CASE %]
|
||||
[% loopfilte.crit | html %]:
|
||||
<strong>[% loopfilte.crit | html %]:</strong>
|
||||
[% loopfilte.filter | html %]
|
||||
[% END %]
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@
|
|||
<span>[% fail.hold_id | html %]</span>
|
||||
[% SWITCH fail.error %]
|
||||
[% CASE 'cannotReserveFromOtherBranches' %]
|
||||
<span>Target item cannot be reserved from other branches</span>
|
||||
<span>Target item cannot be placed on hold from other libraries</span>
|
||||
[% CASE 'ageRestricted' %]
|
||||
<span>The target record and/or items are age restricted</span>
|
||||
[% CASE 'alreadypossession' %]
|
||||
|
|
@ -219,8 +219,6 @@
|
|||
<span>Target item has too many holds placed on it</span>
|
||||
[% CASE 'notReservable' %]
|
||||
<span>Target item is not reservable</span>
|
||||
[% CASE 'cannotReserveFromOtherBranches' %]
|
||||
<span>Target item cannot be placed on reserve for this branch</span>
|
||||
[% CASE 'branchNotInHoldGroup' %]
|
||||
<span>Tagret item is not in the local hold group</span>
|
||||
[% CASE 'notforloan' %]
|
||||
|
|
@ -1607,7 +1605,7 @@
|
|||
tooManyReservesToday: _("Daily hold limit reached for patron"),
|
||||
tooManyReserves: _("Too many holds"),
|
||||
notReservable: _("Not holdable"),
|
||||
noReservesAllowed: _("No reserves allowed"),
|
||||
noReservesAllowed: _("No holds allowed"),
|
||||
cannotReserveFromOtherBranches: _("Patron is from different library"),
|
||||
itemAlreadyOnHold: _("Patron already has hold for this item"),
|
||||
cannotBeTransferred: _("Cannot be transferred to pickup library"),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
/* global APIClient __ confirmDelete */
|
||||
$(document).ready(function () {
|
||||
const client = APIClient.recall;
|
||||
|
||||
$(".cancel_recall").click(function (e) {
|
||||
$(".cancel_recall").click(function () {
|
||||
if (confirmDelete(__("Are you sure you want to remove this recall?"))) {
|
||||
let td_node = $(this).parents("td");
|
||||
let recall_id = $(this).data("id");
|
||||
let message;
|
||||
client.recalls.cancel(recall_id).then(
|
||||
success => {
|
||||
if (success.success == 0) {
|
||||
|
|
@ -23,10 +25,11 @@ $(document).ready(function () {
|
|||
}
|
||||
});
|
||||
|
||||
$(".expire_recall").click(function (e) {
|
||||
$(".expire_recall").click(function () {
|
||||
if (confirmDelete(__("Are you sure you want to expire this recall?"))) {
|
||||
let td_node = $(this).parents("td");
|
||||
let recall_id = $(this).data("id");
|
||||
let message;
|
||||
client.recalls.expire(recall_id).then(
|
||||
success => {
|
||||
if (success.success == 0) {
|
||||
|
|
@ -45,7 +48,7 @@ $(document).ready(function () {
|
|||
}
|
||||
});
|
||||
|
||||
$(".revert_recall").click(function (e) {
|
||||
$(".revert_recall").click(function () {
|
||||
if (
|
||||
confirmDelete(
|
||||
__(
|
||||
|
|
@ -55,6 +58,7 @@ $(document).ready(function () {
|
|||
) {
|
||||
let td_node = $(this).parents("td");
|
||||
let recall_id = $(this).data("id");
|
||||
let message;
|
||||
client.recalls.revert(recall_id).then(
|
||||
success => {
|
||||
if (success.success == 0) {
|
||||
|
|
@ -73,7 +77,7 @@ $(document).ready(function () {
|
|||
}
|
||||
});
|
||||
|
||||
$(".overdue_recall").click(function (e) {
|
||||
$(".overdue_recall").click(function () {
|
||||
if (
|
||||
confirmDelete(
|
||||
__("Are you sure you want to mark this recall as overdue?")
|
||||
|
|
@ -81,6 +85,7 @@ $(document).ready(function () {
|
|||
) {
|
||||
let td_node = $(this).parents("td");
|
||||
let recall_id = $(this).data("id");
|
||||
let message;
|
||||
client.recalls.overdue(recall_id).then(
|
||||
success => {
|
||||
if (success.success == 0) {
|
||||
|
|
@ -99,7 +104,7 @@ $(document).ready(function () {
|
|||
}
|
||||
});
|
||||
|
||||
$(".transit_recall").click(function (e) {
|
||||
$(".transit_recall").click(function () {
|
||||
if (
|
||||
confirmDelete(
|
||||
__(
|
||||
|
|
@ -109,6 +114,7 @@ $(document).ready(function () {
|
|||
) {
|
||||
let td_node = $(this).parents("td");
|
||||
let recall_id = $(this).data("id");
|
||||
let message;
|
||||
client.recalls.transit(recall_id).then(
|
||||
success => {
|
||||
if (success.success == 0) {
|
||||
|
|
@ -132,7 +138,7 @@ $(document).ready(function () {
|
|||
pagingType: "full_numbers",
|
||||
});
|
||||
|
||||
$("#cancel_selected").click(function (e) {
|
||||
$("#cancel_selected").click(function () {
|
||||
if ($("input[name='recall_ids']:checked").length > 0) {
|
||||
return confirmDelete(
|
||||
__("Are you sure you want to remove the selected recall(s)?")
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ let operationLabels = {
|
|||
connection: __("Connection"),
|
||||
upload: __("Upload"),
|
||||
download: __("Download"),
|
||||
list: __("List Files"),
|
||||
change_directory: __("Change Directory"),
|
||||
list: __("List files"),
|
||||
change_directory: __("Change directory"),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -907,6 +907,12 @@ div {
|
|||
background-color: #333333;
|
||||
}
|
||||
|
||||
.library-description {
|
||||
& + .library-description {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset {
|
||||
&.rows {
|
||||
clear: left;
|
||||
|
|
|
|||
|
|
@ -1360,10 +1360,9 @@
|
|||
<td class="location" property="seller" data-order="[% holding_library_name | html %]">
|
||||
<link property="itemOffered" href="#record" />
|
||||
<link property="businessFunction" href="http://purl.org/goodrelations/v1#LeaseOut" />
|
||||
|
||||
[% IF ( ITEM_RESULT.holding_library_info ) %]
|
||||
<a href="[% holding_library_url | url %]" class="library_info" data-info="[% ITEM_RESULT.holding_library_info | html %]" data-name="[% holding_library_name | html %]">
|
||||
<i class="fa fa-info-circle" aria-hidden="true"></i> [% holding_library_name | html %]
|
||||
<a href="[% holding_library_url | url %]" class="library_info" data-index="[% loop.index | html %]" data-name="[% holding_library_name | html %]">
|
||||
<i class="fa fa-info-circle" aria-hidden="true"></i> [% holding_library_name | html %][% my_library_info | html %]
|
||||
</a>
|
||||
[% ELSIF holding_library_url %]
|
||||
<a href="[% holding_library_url | url %]"> [% holding_library_name | html %] </a>
|
||||
|
|
@ -1389,7 +1388,7 @@
|
|||
<link property="businessFunction" href="http://purl.org/goodrelations/v1#LeaseOut" />
|
||||
|
||||
[% IF ( ITEM_RESULT.home_library_info ) %]
|
||||
<a href="[% home_library_url | url %]" class="library_info" data-info="[% ITEM_RESULT.home_library_info | html %]" data-name="[% home_library_name | html %]">
|
||||
<a href="[% home_library_url | url %]" class="library_info" data-index="[% loop.index | html %]" data-name="[% home_library_name | html %]">
|
||||
<i class="fa fa-info-circle" aria-hidden="true"></i> [% home_library_name | html %]
|
||||
</a>
|
||||
[% ELSIF home_library_url %]
|
||||
|
|
@ -1604,6 +1603,16 @@
|
|||
[% END %]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
[% FOREACH ITEM_RESULT IN items %]
|
||||
[% IF ( ITEM_RESULT.holding_library_info ) %]
|
||||
<div style="display:none" id="library_info_[% loop.index | html %]">
|
||||
[% FOREACH info IN ITEM_RESULT.holding_library_info %]
|
||||
<div id="library_description_[% info.additional_content_id | $raw %]" class="library-description">[% info.content | $raw %]</div>
|
||||
[% END %]
|
||||
</div>
|
||||
[% END %]
|
||||
[% END %]
|
||||
[% END %][%# end of items_table block %]
|
||||
|
||||
[% IF Koha.Preference('OpacCatalogConcerns') %]
|
||||
|
|
@ -2189,41 +2198,18 @@
|
|||
$(".library_info").on("click", function(e){
|
||||
e.preventDefault();
|
||||
var library_name = $(this).data("name");
|
||||
var opac_info = $(this).data("info");
|
||||
var index = $(this).data("index");
|
||||
var url = $(this).attr("href");
|
||||
if( 1 ) {
|
||||
$("#libraryInfoModalLabel").html( library_name );
|
||||
$("#libraryInfo").html( opac_info );
|
||||
if( url ){
|
||||
$("#libraryInfoLink").attr("href", url );
|
||||
$("#libraryInfoLink").show();
|
||||
} else {
|
||||
$("#libraryInfoLink").hide();
|
||||
}
|
||||
$("#libraryInfoModal").modal("show");
|
||||
} else { // FIXME Temporary disabled: see BZ 29144
|
||||
$.ajax({
|
||||
url: "/api/v1/public/libraries/" + branchcode,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(result) {
|
||||
$("#libraryInfoModalLabel").html( result.name );
|
||||
$("#libraryInfo").html( opac_info );
|
||||
if( result.url ){
|
||||
$("#libraryInfoLink").attr("href", result.url );
|
||||
$("#libraryInfoLink").show();
|
||||
} else {
|
||||
$("#libraryInfoLink").hide();
|
||||
}
|
||||
$("#libraryInfoModal").modal("show");
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
if( url ){
|
||||
location.href = url;
|
||||
}
|
||||
}
|
||||
});
|
||||
let library_info = $("#library_info_" + index).html();
|
||||
$("#libraryInfoModalLabel").html( library_name );
|
||||
$("#libraryInfo").html( library_info );
|
||||
if( url ){
|
||||
$("#libraryInfoLink").attr("href", url );
|
||||
$("#libraryInfoLink").show();
|
||||
} else {
|
||||
$("#libraryInfoLink").hide();
|
||||
}
|
||||
$("#libraryInfoModal").modal("show");
|
||||
});
|
||||
$("#libraryInfoModal").on("hide.bs.modal", function(){
|
||||
$("#libraryInfoModalLabel, #libraryInfo").html("");
|
||||
|
|
|
|||
|
|
@ -36,7 +36,11 @@
|
|||
[% INCLUDE 'masthead.inc' %]
|
||||
|
||||
[% BLOCK library_description %]
|
||||
<div property="description"> [% IF OpacLibraryInfo %][% OpacLibraryInfo.content | $raw %][% END %] </div>
|
||||
<div property="description">
|
||||
[% FOREACH info IN OpacLibraryInfo %]
|
||||
<div id="library_description_[% info.additional_content_id | $raw %]" class="library-description">[% info.content | $raw %]</div>
|
||||
[% END %]
|
||||
</div>
|
||||
[% END %]
|
||||
|
||||
[% BLOCK library_info %]
|
||||
|
|
|
|||
|
|
@ -155,27 +155,6 @@
|
|||
if ( prod.primaryCreator ) results.push( '<p>' + _("by") + ' ', prod.primaryCreator.name, '</p>' );
|
||||
results.push( '<span class="results_summary mediatype"><span class="label">' + _("Type:") + ' </span>', prod.mediaType, '</span>' );
|
||||
|
||||
if ( prod.starRating ) {
|
||||
var rating_value = Math.round( prod.starRating );
|
||||
results.push( '<div class="results_summary ratings">' );
|
||||
results.push( '<div class="br-wrapper br-theme-fontawesome-stars">');
|
||||
results.push( '<div class="br-widget br-readonly">');
|
||||
for ( var rating = 1; rating <= 5; rating++ ) {
|
||||
|
||||
results.push( '<a href="#"' );
|
||||
if( rating == rating_value ){
|
||||
results.push( ' class="br-selected br-current"></a>' );
|
||||
} else if( rating_value > rating ){
|
||||
results.push( ' class="br-selected"></a>' );
|
||||
} else {
|
||||
results.push( '></a>');
|
||||
}
|
||||
}
|
||||
results.push( '</div>' );
|
||||
results.push( '</div>' );
|
||||
results.push( '</div>' );
|
||||
}
|
||||
|
||||
results.push( '</td>' );
|
||||
|
||||
results.push( '<td>' );
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ use Modern::Perl;
|
|||
use Try::Tiny;
|
||||
|
||||
# external modules
|
||||
use CGI qw ( -utf8 );
|
||||
use CGI qw ( -utf8 );
|
||||
use Scalar::Util qw( blessed );
|
||||
|
||||
# internal modules
|
||||
use C4::Auth qw( get_template_and_user haspermission );
|
||||
|
|
@ -52,7 +53,8 @@ use Koha::SMS::Providers;
|
|||
my $input = CGI->new;
|
||||
my %data;
|
||||
|
||||
my $dbh = C4::Context->dbh;
|
||||
my $dbh = C4::Context->dbh;
|
||||
my $logger = Koha::Logger->get( { interface => 'intranet ' } );
|
||||
|
||||
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
|
||||
{
|
||||
|
|
@ -273,29 +275,61 @@ if (@delete_guarantor) {
|
|||
if ( C4::Context->preference('ChildNeedsGuarantor') && $will_remove_last ) {
|
||||
push @errors, 'ERROR_cannot_delete_guarantor';
|
||||
} else {
|
||||
my @deleted_guarantors;
|
||||
foreach my $id (@delete_guarantor) {
|
||||
my $r = Koha::Patron::Relationships->find($id);
|
||||
$r->delete() if $r;
|
||||
if ($r) {
|
||||
$r->delete();
|
||||
push @deleted_guarantors, $id;
|
||||
}
|
||||
}
|
||||
foreach my $deleted_guarantor_id (@deleted_guarantors) {
|
||||
@guarantors = grep { ref($_) eq 'Koha::Patron' && $_->id ne $deleted_guarantor_id } @guarantors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Check if guarantor requirements are met
|
||||
my $valid_guarantor = @guarantors ? @guarantors : $newdata{'contactname'};
|
||||
if ( ( $op eq 'cud-save' || $op eq 'cud-insert' )
|
||||
&& C4::Context->preference('ChildNeedsGuarantor')
|
||||
&& ( $category->category_type eq 'C' || $category->can_be_guarantee )
|
||||
&& !$valid_guarantor )
|
||||
{
|
||||
push @errors, 'ERROR_child_no_guarantor';
|
||||
}
|
||||
my $guarantors_to_be_validated =
|
||||
@guarantors ? \@guarantors : $newdata{'contactname'} ? [ $newdata{'contactname'} ] : [];
|
||||
if ( ( $op eq 'cud-save' || $op eq 'cud-insert' ) && $guarantors_to_be_validated ) {
|
||||
try {
|
||||
if ($patron) {
|
||||
if ( $patron->categorycode eq $categorycode ) {
|
||||
$patron->can_be_guaranteed_by($guarantors_to_be_validated);
|
||||
} else {
|
||||
|
||||
foreach my $guarantor (@guarantors) {
|
||||
if ( ( $op eq 'cud-save' || $op eq 'cud-insert' )
|
||||
&& ( $guarantor->is_child || $guarantor->is_guarantee || ( $patron && $patron->is_guarantor ) ) )
|
||||
{
|
||||
push @errors, 'ERROR_guarantor_is_guarantee';
|
||||
}
|
||||
# If $patron's categorycode is about to change, check guarantor requirements against
|
||||
# the new category but keep the original $patron object intact
|
||||
# (this clone()s the $patron)
|
||||
( bless {%$patron}, ref $patron )->categorycode($categorycode)
|
||||
->can_be_guaranteed_by($guarantors_to_be_validated);
|
||||
}
|
||||
} else {
|
||||
Koha::Patron->new( { categorycode => $categorycode } )->can_be_guaranteed_by($guarantors_to_be_validated);
|
||||
}
|
||||
} catch {
|
||||
unless ( blessed($_) ) {
|
||||
$logger->error($_);
|
||||
} else {
|
||||
if ( $_->isa("Koha::Exceptions::Patron::Relationship::NoGuarantor") ) {
|
||||
push @errors, "ERROR_child_no_guarantor";
|
||||
} elsif ( $_->isa("Koha::Exceptions::Patron::Relationship::InvalidRelationship") ) {
|
||||
if ( $_->invalid_guarantor ) {
|
||||
push @errors, "ERROR_guarantor_is_guarantee";
|
||||
$template->param( guarantor_is_guarantee => $_->guarantor );
|
||||
} elsif ( $_->child_guarantor ) {
|
||||
push @errors, "ERROR_child_is_guarantor";
|
||||
} elsif ( $_->guarantor_cant_have_guarantors ) {
|
||||
push @errors, "ERROR_guarantor_cant_have_guarantors";
|
||||
}
|
||||
} else {
|
||||
$logger->error( $_->error );
|
||||
$_->rethrow;
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
my @valid_relationships = split( /\|/, C4::Context->preference('borrowerRelationship'), -1 );
|
||||
|
|
@ -447,7 +481,7 @@ if ( ( !$nok ) and $nodouble and ( $op eq 'cud-insert' or $op eq 'cud-save' ) )
|
|||
delete $newdata{password2};
|
||||
$success = 1;
|
||||
$patron = try {
|
||||
Koha::Patron->new( \%newdata )->store( { guarantors => \@guarantors } );
|
||||
Koha::Patron->new( \%newdata )->store();
|
||||
} catch {
|
||||
$success = 0;
|
||||
$nok = 1;
|
||||
|
|
@ -561,7 +595,7 @@ if ( ( !$nok ) and $nodouble and ( $op eq 'cud-insert' or $op eq 'cud-save' ) )
|
|||
delete $newdata{guarantor_relationship};
|
||||
|
||||
try {
|
||||
$patron->set( \%newdata )->store( { guarantors => \@guarantors } ) if scalar( keys %newdata ) > 1;
|
||||
$patron->set( \%newdata )->store() if scalar( keys %newdata ) > 1;
|
||||
|
||||
# bug 4508 - avoid crash if we're not updating any columns in the borrowers table (editing patron attrs or msg prefs)
|
||||
$success = 1;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,674 @@
|
|||
<h1 id="releasenotesforkoha25.11.01">RELEASE NOTES FOR KOHA 25.11.01</h1>
|
||||
|
||||
<p>28 Jan 2026</p>
|
||||
|
||||
<p>Koha is the first free and open source software library automation
|
||||
package (ILS). Development is sponsored by libraries of varying types
|
||||
and sizes, volunteers, and support companies from around the world. The
|
||||
website for the Koha project is:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://koha-community.org">Koha Community</a></li>
|
||||
</ul>
|
||||
|
||||
<p>Koha 25.11.01 can be downloaded from:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://download.koha-community.org/koha-25.11.01.tar.gz">Download</a></li>
|
||||
</ul>
|
||||
|
||||
<p>Installation instructions can be found at:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://wiki.koha-community.org/wiki/Installation_Documentation">Koha Wiki</a></li>
|
||||
<li>OR in the INSTALL files that come in the tarball</li>
|
||||
</ul>
|
||||
|
||||
<p>Koha 25.11.01 is a bugfix/maintenance release.</p>
|
||||
|
||||
<p>It includes 34 bugfixes.</p>
|
||||
|
||||
<p><strong>System requirements</strong></p>
|
||||
|
||||
<p>You can learn about the system components (like OS and database) needed for running Koha on the <a href="https://wiki.koha-community.org/wiki/System_requirements_and_recommendations">community wiki</a>.</p>
|
||||
|
||||
<h4 id="securitybugs">Security bugs</h4>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41593">41593</a> Authenticated SQL Injection in staff side suggestions</li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41662">41662</a> CSRF-vulnerability in opac-patron-consent.pl.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="bugfixes">Bugfixes</h2>
|
||||
|
||||
<h3 id="acquisitions">Acquisitions</h3>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39514">39514</a> If one basket has uncertain prices, all baskets are displayed in red</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes the display of baskets in acquisitions so that only baskets with uncertain prices are shown in red. Previously, if one basket had an uncertain price, all the baskets in the page were shown in red, even those without uncertain prices, making it hard to know where to go to fix the price.</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="architectureinternalsandplumbing">Architecture, internals, and plumbing</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="criticalbugsfixed">Critical bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=38426">38426</a> Node.js v18 EOL around 25.05 release time</li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40989">40989</a> t/db_dependent/OAI/Server.t fails on Debian 13</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes OAI-PMH (tests and request) so that OAI-PMH responses work when Debian 13 (Trixie) is used as the operating system. Trailing slashes are stripped from requestURL for CGI.pm 4.68+ compatibility</p>
|
||||
|
||||
<p>Technical details:</p>
|
||||
|
||||
<p>CGI.pm 4.68 (shipped with Debian 13/Trixie) changed the behaviour of
|
||||
self_url() to include a trailing slash when there's no path component.</p>
|
||||
|
||||
<p>This 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.</li>
|
||||
</ul></p>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
</blockquote>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41238">41238</a> Pseudonymize statistic jobs don't update progress</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes the progress shown for pseudonymize statistics background jobs. The progress was shown in the list of jobs (Administration > Jobs > Manage jobs) as "0/1" instead of "1/1", even though the background job was finished.</li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41404">41404</a> No need to check related guarantor/guarantee charges when the limits are not set</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="circulation">Circulation</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40949">40949</a> Bookings to collect shouldn't tell staff to check in items</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This removes the sentence "Please retrieve them and check them in." from the bookings to collect page (Circulation > Holds and bookings > Bookings to collect). This is not required, as checking in items is not relevant for bookings.</li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41345">41345</a> Regression: Clicking the 'Ignore' button on hold found modal for already-waiting hold does not dismiss the modal (again)
|
||||
This fixes a regression when checking in an item. Clicking the "Ignore" option in the dialog box, when an item already has a waiting status, just reloaded the dialog box. Clicking the "Ignore" option now closes the dialog box and works as expected.</li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41352">41352</a> Bookings to Collect Help does not take you to the correct place in the manual
|
||||
This fixes the link to the help for the Circulation > Holds and bookings > Bookings to collect page - it now links to the correct place in the documentation, instead of the documentation home page.</li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41451">41451</a> Hold history search fails when itemtype column present
|
||||
This fixes filtering on a patron's holds history when AllowHoldItemTypeSelection is enabled. This previously produced a 500 error when searching, but now works as expected.</li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41456">41456</a> Item type filter on the hold history view does not work correctly
|
||||
This fixes the patron's holds history table in the staff interface. The search filter now works as expected - using the library name or library code and requested item type name or item type code (when AllowHoldItemTypeSelection is enabled) now work as expected, and the column filters for both of these now use dropdown lists.</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="holdrequests">Hold requests</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41432">41432</a> Add prefetch to improve performance of holds page</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This improves the performance of the holds request page for a record in the staff interface.</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="i18nl10n">I18N/L10N</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<ul>
|
||||
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40287">40287</a> Fix untranslatable strings in more statistics wizards</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes and improves the acquisitions, patrons, catalog, and circulation statistics report wizard:</p>
|
||||
|
||||
<ul>
|
||||
<li>Fixes some strings so that they are now translatable</li>
|
||||
<li>Improves the "Filtered on" information shown before the report results:
|
||||
. the filtered on options selected in the report are now shown in a bulleted list and in bold
|
||||
. descriptions are now shown instead of codes (for example, the library name instead of the library code)</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
|
||||
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
|
||||
</ul>
|
||||
|
||||
<h3 id="installationandupgradeweb-basedinstaller">Installation and upgrade (web-based installer)</h3>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40006">40006</a> Upgrading install.pl shows code vs HTML</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes a database update (for bug 38436 in 24.11.00) that caused code to show in the browser instead of HTML output when running an upgrade.</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="opac">OPAC</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<ul>
|
||||
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40619">40619</a> Remove OverDrive star ratings from the OPAC</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This removes the code for OverDrive star ratings from OPAC pages, as OverDrive no longer supplies star ratings through their API.</p>
|
||||
</blockquote>
|
||||
|
||||
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
|
||||
</ul>
|
||||
|
||||
<h3 id="patrons">Patrons</h3>
|
||||
|
||||
<h4 id="criticalbugsfixed">Critical bugs fixed</h4>
|
||||
|
||||
<ul>
|
||||
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41145">41145</a> Logging patron attributes logs even if there's no changes</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This prevents misleading patron attribute modification logs, when a library batch imports patrons with the BorrowersLog system preference set to 'Log'. It now correctly only shows a log entry when a patron attribute value is changed.</p>
|
||||
|
||||
<p>Example: </p>
|
||||
|
||||
<ul>
|
||||
<li>Before the change: for an existing patron with a patron attribute of INSTID:1234, with a re-import the log shows { "attribute.INSTID" : { "after" : "1234", "before" : "" } }, even though there is no change to the patron attribute.</li>
|
||||
<li>After the change:
|
||||
. No log entry is shown if there is no change to the patron attribute.
|
||||
. If there is a change to the patron attribute (for example, changed to 5678 on a re-import), it is now correctly shown - { "attribute.INSTID" : { "after" : "5678", "before" : "1234" } }</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
|
||||
<p><strong>Sponsored by</strong> <em>Auckland University of Technology</em></p></li>
|
||||
</ul>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<ul>
|
||||
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39014">39014</a> Storing a guarantee fails due to TrackLastPatronActivityTriggers "creating a patron"</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes an error when creating a patron that requires a guarantor, when this combination of settings is used:</p>
|
||||
|
||||
<ul>
|
||||
<li><code>TrackLastPatronActivityTriggers</code>: 'Creating a patron' is selected</li>
|
||||
<li><code>ChildNeedsGuarantor</code>: is set to 'requires'</li>
|
||||
<li>The patron category is a 'Child' and 'Can be guarantee' is set to 'Yes'</li>
|
||||
</ul>
|
||||
|
||||
<p>With these settings, this error message "The following fields are wrong. Please fix them." (without a list of fields) was incorrectly shown when creating a child patron, even though you had correctly added the guarantor information.</p>
|
||||
</blockquote>
|
||||
|
||||
<p><strong>Sponsored by</strong> <em>Koha-Suomi Oy</em></p></li>
|
||||
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41363">41363</a> Don't hide patron category limitation warning behind icon</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This moves the hint text for the warning on the Library management > Category field (when editing a patron record) from a tool tip on the warning icon to standard hint text under the input field, to make it more accessible</p>
|
||||
|
||||
<p>Note: This warning only appears under certain circumstances (when a patron category is limited to a specific library, and you edit a patron when the library is set to another location).</p>
|
||||
</blockquote>
|
||||
|
||||
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
|
||||
</ul>
|
||||
|
||||
<h3 id="pointofsale">Point of Sale</h3>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41408">41408</a> POS Inline Editing Triggers Form Submission on Enter Key</p>
|
||||
|
||||
<blockquote>
|
||||
<p>Inline editing the cost or quantity fields on a point of sale transaction, then pressing enter, incorrectly submitted the form--instead of just updating the field.</p>
|
||||
|
||||
<p>Now, the values in the fields are updated without submitting the form, and you can continue entering sale details and completing the transaction.</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="restapi">REST API</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40219">40219</a> Welcome Email Sent on Failed Patron Registration via API</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes patron registrations using the API - a welcome email notice was sent even if there were validation failures.</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="reports">Reports</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41292">41292</a> Add "force_password_reset_when_set_by_staff" to the allowed column name list</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This adds the force_password_reset_when_set_by_staff field in the categories table to the list of allowed password-related fields that can be used in SQL reports.</p>
|
||||
|
||||
<p>Currently, this field is treated as containing sensitive password-related data and generates an error when creating a report that uses it.</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="sip2">SIP2</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40455">40455</a> A patron information request fails when no currency is set</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes an error in SIP2 patron information responses (64) when no currency is set, which can be the case for new Koha instances.</p>
|
||||
|
||||
<p>Example, if no currency is set:
|
||||
- Before the fix, there is an 'undef' error:
|
||||
...
|
||||
Trying 'patron_information'
|
||||
SEND: 6300120251202 115652 AOCPL|AA42|ACterm1|
|
||||
READ: undef
|
||||
- After the fix, patron informatin is returned and there is no error:
|
||||
...
|
||||
Trying 'patron_information'
|
||||
SEND: 6300120251202 122545 AOCPL|AA42|ACterm1|
|
||||
READ: 64 00120251202 <br />
|
||||
122546000000000000000000000000AOCPL|AA42|AE koha|BLY|BV0|CC5|PCS|
|
||||
PIY|AFGreetings from Koha. |</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="searching-elasticsearch">Searching - Elasticsearch</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=38345">38345</a> Restore support for OpenSearch</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes Koha so that OpenSearch now works, and is now covered again by continuous integration tests (these have been failing for some time in Jenkins).</p>
|
||||
|
||||
<p>Note: See the test plan for guidance on how to start KTD successfully with OS1 and OS2 if you have issues.</li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40980">40980</a> Clicking a search facet without logging in may trigger a cud-login error
|
||||
This fixes using facets in the OPAC for searching when not logged in, where Elasticsearch or OpenSearch is used as the search engine. In some circumstances, a 403 Forbidden Error was incorrectly generated (this is related to changes made in previous versions of Koha to improve form security).</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="serials">Serials</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=37796">37796</a> Generated issue has incorrect number in pattern when receiving</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes an issue with incorrect numbering patterns and the next expected issue shown, after a serial is received.</p>
|
||||
|
||||
<p>Example of an incorrect numbering pattern before this fix:
|
||||
- Monthly serial received for August 2025 (its status shows as 'Arrived' for August 2025).
|
||||
- The next expected serial was then shown as August 2025 instead of September 2025 (its status was shown as 'Expected' with a incorrect numbering pattern of August 2025)</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="staffinterface">Staff interface</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<p><ul>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41427">41427</a> Terminology: branch should be library in FilterSearchResultsByLoggedInBranch</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes the terminology and improves the description for the FilterSearchResultsByLoggedInBranch system preference - branch should be library.</li>
|
||||
</ul></p>
|
||||
|
||||
<h3 id="systemadministration">System Administration</h3>
|
||||
</blockquote>
|
||||
|
||||
<h4 id="criticalbugsfixed">Critical bugs fixed</h4>
|
||||
|
||||
<ul>
|
||||
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39482">39482</a> Link to edit OpacLibraryInfo from library edit page broken</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes editing and showing OpacLibraryInfo HTML customizations information in the staff interface and OPAC:</p>
|
||||
|
||||
<ol>
|
||||
<li><p>Staff interface:</p>
|
||||
|
||||
<ul>
|
||||
<li>When editing a library (Koha administration > Libraries > Actions > Edit)
|
||||
. the links to edit OpacLibraryInfo entries for the OPAC information field are now correct (previously, they may have linked to an incorrect HTML customization)
|
||||
. if there is more than one OpacLibraryInfo entry, all entries are now shown (only one entry was shown previously)</li>
|
||||
<li>When viewing library information (Koha administration > Libraries > Name > [click library name]), all the OpacLibraryInfo entries are now shown</li>
|
||||
</ul></li>
|
||||
<li><p>OPAC:</p>
|
||||
|
||||
<ul>
|
||||
<li>All the entries for a library (including any 'All libraries' entries for OpacLibraryInfo) are now shown on the library information page (from the 'Libraries' link under the quick search bar)</li>
|
||||
<li>In a record's holdings table, the pop-up window when you click on the current library for an item now correctly shows all entries</li>
|
||||
</ul></li>
|
||||
</ol>
|
||||
</blockquote>
|
||||
|
||||
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
|
||||
</ul>
|
||||
|
||||
<h3 id="templates">Templates</h3>
|
||||
|
||||
<h4 id="otherbugsfixed">Other bugs fixed</h4>
|
||||
|
||||
<ul>
|
||||
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40567">40567</a> Correct eslint errors in recalls.js</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes a few minor coding guideline errors in the JavaScript used on recalls pages (JS8: Follow guidelines set by ESLint). There are no changes to how the recalls pages work.</p>
|
||||
</blockquote>
|
||||
|
||||
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41339">41339</a> Typo 'Too many checkout'
|
||||
<blockquote>
|
||||
<p>Changes "Too many checkout" to "Too many checkouts" message that shows in the log viewer when circulation overrides are logged.</li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41348">41348</a> Capitalization: "List Files" and others
|
||||
This fixes the capitalization for messages used with FTP/SFTP file transfers (Administration > Additional parameters > File transports).</p>
|
||||
|
||||
<p>Changes:
|
||||
- "List Files" to "List files"
|
||||
- "Change Directory" to "Change directory"</li>
|
||||
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41361">41361</a> Incorrect markup in category code confirmation modal</p></p>
|
||||
</blockquote>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes the "Confirm expiration date" dialog box that is shown when changing an individual patron's category:</p>
|
||||
|
||||
<ul>
|
||||
<li>The "No" option now works.</li>
|
||||
<li>It is now formatted using our standard Bootstrap 5 styles.</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
|
||||
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
|
||||
<li><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41395">41395</a> Terminology: Target item cannot be reserved from other branches
|
||||
<blockquote>
|
||||
<p>Updates the terminology for two circulation-related messages (reserves to holds, branches to libraries):</p>
|
||||
|
||||
<ul>
|
||||
<li>"Target item cannot be reserved from other branches" to "Target item cannot be placed on hold from other libraries"</li>
|
||||
<li>"No reserves allowed" to "No holds allowed"</li>
|
||||
<li><p><a href="https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41396">41396</a> Capitalization: 'Transport Settings' and other</p></li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
|
||||
<blockquote>
|
||||
<p>This fixes the capitalization for the section headings on the EDI account create and edit form - they are now in sentence case (Administration > Acquisition parameters > EDI accounts):</p>
|
||||
|
||||
<ul>
|
||||
<li>Basic Information => Basic information</li>
|
||||
<li>Transport Settings => Transport settings</li>
|
||||
<li>Message Types => Message types</li>
|
||||
<li>Functional Switches => Functional switches</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
|
||||
<p><strong>Sponsored by</strong> <em>Athens County Public Libraries</em></p></li>
|
||||
</ul>
|
||||
|
||||
<h2 id="documentation">Documentation</h2>
|
||||
|
||||
<p>The Koha manual is maintained in Sphinx. The home page for Koha
|
||||
documentation is</p>
|
||||
|
||||
<ul>
|
||||
<li><p><a href="https://koha-community.org/documentation/">Koha Documentation</a>
|
||||
As of the date of these release notes, the Koha manual is available in the following languages:</p></li>
|
||||
<li><p><a href="https://koha-community.org/manual/25.11/en/html/">English (USA)</a></p></li>
|
||||
<li><a href="https://koha-community.org/manual/25.11/fr/html/">French</a> (74%)</li>
|
||||
<li><a href="https://koha-community.org/manual/25.11/de/html/">German</a> (90%)</li>
|
||||
<li><a href="https://koha-community.org/manual/25.11/el/html/">Greek</a> (94%)</li>
|
||||
<li><a href="https://koha-community.org/manual/25.11/hi/html/">Hindi</a> (64%)</li>
|
||||
</ul>
|
||||
|
||||
<p>The Git repository for the Koha manual can be found at</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://gitlab.com/koha-community/koha-manual">Koha Git Repository</a></li>
|
||||
</ul>
|
||||
|
||||
<h2 id="translations">Translations</h2>
|
||||
|
||||
<p>Complete or near-complete translations of the OPAC and staff
|
||||
interface are available in this release for the following languages:</p>
|
||||
|
||||
<div style="column-count: 2;">
|
||||
|
||||
- Arabic (ar_ARAB) (90%)
|
||||
- Armenian (hy_ARMN) (100%)
|
||||
- Bulgarian (bg_CYRL) (100%)
|
||||
- Chinese (Simplified Han script) (82%)
|
||||
- Chinese (Traditional Han script) (95%)
|
||||
- Czech (65%)
|
||||
- Dutch (85%)
|
||||
- English (100%)
|
||||
- English (New Zealand) (60%)
|
||||
- English (USA)
|
||||
- Finnish (99%)
|
||||
- French (99%)
|
||||
- French (Canada) (97%)
|
||||
- German (99%)
|
||||
- Greek (64%)
|
||||
- Hindi (93%)
|
||||
- Italian (79%)
|
||||
- Norwegian Bokmål (69%)
|
||||
- Persian (fa_ARAB) (91%)
|
||||
- Polish (99%)
|
||||
- Portuguese (Brazil) (99%)
|
||||
- Portuguese (Portugal) (86%)
|
||||
- Russian (91%)
|
||||
- Slovak (57%)
|
||||
- Spanish (96%)
|
||||
- Swedish (88%)
|
||||
- Telugu (64%)
|
||||
- Turkish (79%)
|
||||
- Ukrainian (73%)
|
||||
- Western Armenian (hyw_ARMN) (59%)
|
||||
</div>
|
||||
|
||||
<p>Partial translations are available for various other languages.</p>
|
||||
|
||||
<p>The Koha team welcomes additional translations; please see</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://wiki.koha-community.org/wiki/Translating_Koha">Koha Translation Info</a></li>
|
||||
</ul>
|
||||
|
||||
<p>For information about translating Koha, and join the koha-translate
|
||||
list to volunteer:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://lists.koha-community.org/cgi-bin/mailman/listinfo/koha-translate">Koha Translate List</a></li>
|
||||
</ul>
|
||||
|
||||
<p>The most up-to-date translations can be found at:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://translate.koha-community.org/">Koha Translation</a></li>
|
||||
</ul>
|
||||
|
||||
<h2 id="releaseteam">Release Team</h2>
|
||||
|
||||
<p>The release team for Koha 25.11.01 is</p>
|
||||
|
||||
<ul>
|
||||
<li><p>Release Manager: Lucas Gass</p></li>
|
||||
<li><p>QA Manager: Martin Renvoize</p></li>
|
||||
<li><p>QA Team:</p>
|
||||
|
||||
<ul>
|
||||
<li>Marcel de Rooy</li>
|
||||
<li>Martin Renvoize</li>
|
||||
<li>Jonathan Druart</li>
|
||||
<li>Laura Escamilla</li>
|
||||
<li>Lucas Gass</li>
|
||||
<li>Tomás Cohen Arazi</li>
|
||||
<li>Lisette Scheer</li>
|
||||
<li>Nick Clemens</li>
|
||||
<li>Paul Derscheid</li>
|
||||
<li>Emily Lamancusa</li>
|
||||
<li>David Cook</li>
|
||||
<li>Matt Blenkinsop</li>
|
||||
<li>Andrew Fuerste-Henry</li>
|
||||
<li>Brendan Lawlor</li>
|
||||
<li>Pedro Amorim</li>
|
||||
<li>Kyle M Hall</li>
|
||||
<li>Aleisha Amohia</li>
|
||||
<li>David Nind</li>
|
||||
<li>Baptiste Wojtkowski</li>
|
||||
<li>Jan Kissig</li>
|
||||
<li>Katrin Fischer</li>
|
||||
<li>Thomas Klausner</li>
|
||||
<li>Julian Maurice</li>
|
||||
<li>Owen Leonard</li>
|
||||
</ul></li>
|
||||
<li><p>Documentation Manager: David Nind</p></li>
|
||||
<li><p>Documentation Team:</p>
|
||||
|
||||
<ul>
|
||||
<li>Philip Orr</li>
|
||||
<li>Aude Charillon</li>
|
||||
<li>Caroline Cyr La Rose</li>
|
||||
</ul></li>
|
||||
<li><p>Translation Manager: Jonathan Druart</p></li>
|
||||
<li><p>Wiki curators: </p>
|
||||
|
||||
<ul>
|
||||
<li>George Williams</li>
|
||||
<li>Thomas Dukleth</li>
|
||||
</ul></li>
|
||||
<li><p>Release Maintainers:</p>
|
||||
|
||||
<ul>
|
||||
<li>25.11 -- Jacob O'Mara</li>
|
||||
<li>25.05 -- Laura Escamilla</li>
|
||||
<li>24.11 -- Fridolin Somers</li>
|
||||
<li>22.11 -- Wainui Witika-Park (Catalyst IT)</li>
|
||||
</ul></li>
|
||||
<li><p>Release Maintainer assistants:</p>
|
||||
|
||||
<ul>
|
||||
<li>25.11 -- Chloé Zermatten</li>
|
||||
<li>24.11 -- Baptiste Wojtkowski</li>
|
||||
<li>22.11 -- Alex Buckley & Aleisha Amohia</li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
|
||||
<h2 id="credits">Credits</h2>
|
||||
|
||||
<p>We thank the following libraries, companies, and other institutions who are known to have sponsored
|
||||
new features in Koha 25.11.01</p>
|
||||
|
||||
<div style="column-count: 2;">
|
||||
|
||||
- Athens County Public Libraries
|
||||
- Auckland University of Technology
|
||||
- [Koha-Suomi Oy](https://koha-suomi.fi)
|
||||
</div>
|
||||
|
||||
<p>We thank the following individuals who contributed patches to Koha 25.11.01</p>
|
||||
|
||||
<div style="column-count: 2;">
|
||||
|
||||
- Pedro Amorim (1)
|
||||
- Tomás Cohen Arazi (2)
|
||||
- Alex Buckley (2)
|
||||
- Nick Clemens (6)
|
||||
- David Cook (2)
|
||||
- Paul Derscheid (2)
|
||||
- Jonathan Druart (7)
|
||||
- Andrew Fuerste-Henry (2)
|
||||
- Lucas Gass (3)
|
||||
- Jan Kissig (1)
|
||||
- Owen Leonard (10)
|
||||
- David Nind (2)
|
||||
- Jacob O'Mara (4)
|
||||
- Martin Renvoize (3)
|
||||
- Marcel de Rooy (1)
|
||||
- Caroline Cyr La Rose (1)
|
||||
- Lisette Scheer (1)
|
||||
- Leo Stoyanov (1)
|
||||
- Emmi Takkinen (1)
|
||||
- Lari Taskula (1)
|
||||
</div>
|
||||
|
||||
<p>We thank the following libraries, companies, and other institutions who contributed
|
||||
patches to Koha 25.11.01</p>
|
||||
|
||||
<div style="column-count: 2;">
|
||||
|
||||
- Athens County Public Libraries (10)
|
||||
- [ByWater Solutions](https://bywatersolutions.com) (13)
|
||||
- [Catalyst](https://www.catalyst.net.nz/products/library-management-koha) (2)
|
||||
- David Nind (2)
|
||||
- [Hypernova Oy](https://www.hypernova.fi) (1)
|
||||
- Koha Community Developers (7)
|
||||
- [Koha-Suomi Oy](https://koha-suomi.fi) (1)
|
||||
- [LMSCloud](https://www.lmscloud.de) (2)
|
||||
- [Open Fifth](https://openfifth.co.uk/) (8)
|
||||
- [Prosentient Systems](https://www.prosentient.com.au) (2)
|
||||
- Rijksmuseum, Netherlands (1)
|
||||
- [Solutions inLibro inc](https://inlibro.com) (1)
|
||||
- [Theke Solutions](https://theke.io) (2)
|
||||
- Wildau University of Technology (1)
|
||||
</div>
|
||||
|
||||
<p>We also especially thank the following individuals who tested patches
|
||||
for Koha</p>
|
||||
|
||||
<div style="column-count: 2;">
|
||||
|
||||
- Christopher Brannon (1)
|
||||
- Aude Charillon (1)
|
||||
- Jonathan Druart (3)
|
||||
- Magnus Enger (1)
|
||||
- Laura Escamilla (8)
|
||||
- Lucas Gass (42)
|
||||
- Victor Grousset (1)
|
||||
- Owen Leonard (1)
|
||||
- David Nind (29)
|
||||
- Jacob O'Mara (49)
|
||||
- Wesley Owen (2)
|
||||
- Martin Renvoize (12)
|
||||
- Marcel de Rooy (11)
|
||||
- Samuel (1)
|
||||
- Lisette Scheer (3)
|
||||
</div>
|
||||
|
||||
<p>We regret any omissions. If a contributor has been inadvertently missed,
|
||||
please send a patch against these release notes to koha-devel@lists.koha-community.org.</p>
|
||||
|
||||
<h2 id="revisioncontrolnotes">Revision control notes</h2>
|
||||
|
||||
<p>The Koha project uses Git for version control. The current development
|
||||
version of Koha can be retrieved by checking out the main branch of:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://git.koha-community.org/koha-community/koha">Koha Git Repository</a></li>
|
||||
</ul>
|
||||
|
||||
<p>The branch for this version of Koha and future bugfixes in this release
|
||||
line is 25.11.x.</p>
|
||||
|
||||
<h2 id="bugsandfeaturerequests">Bugs and feature requests</h2>
|
||||
|
||||
<p>Bug reports and feature requests can be filed at the Koha bug
|
||||
tracker at:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://bugs.koha-community.org">Koha Bugzilla</a></li>
|
||||
</ul>
|
||||
|
||||
<p>He rau ringa e oti ai.
|
||||
(Many hands finish the work)</p>
|
||||
|
||||
<p>Autogenerated release notes updated last on 28 Jan 2026 11:17:10.</p>
|
||||
|
|
@ -0,0 +1,514 @@
|
|||
# RELEASE NOTES FOR KOHA 25.11.01
|
||||
28 Jan 2026
|
||||
|
||||
Koha is the first free and open source software library automation
|
||||
package (ILS). Development is sponsored by libraries of varying types
|
||||
and sizes, volunteers, and support companies from around the world. The
|
||||
website for the Koha project is:
|
||||
|
||||
- [Koha Community](https://koha-community.org)
|
||||
|
||||
Koha 25.11.01 can be downloaded from:
|
||||
|
||||
- [Download](https://download.koha-community.org/koha-25.11.01.tar.gz)
|
||||
|
||||
Installation instructions can be found at:
|
||||
|
||||
- [Koha Wiki](https://wiki.koha-community.org/wiki/Installation_Documentation)
|
||||
- OR in the INSTALL files that come in the tarball
|
||||
|
||||
Koha 25.11.01 is a bugfix/maintenance release.
|
||||
|
||||
It includes 34 bugfixes.
|
||||
|
||||
**System requirements**
|
||||
|
||||
You can learn about the system components (like OS and database) needed for running Koha on the [community wiki](https://wiki.koha-community.org/wiki/System_requirements_and_recommendations).
|
||||
|
||||
|
||||
#### Security bugs
|
||||
|
||||
- [41593](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41593) Authenticated SQL Injection in staff side suggestions
|
||||
- [41662](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41662) CSRF-vulnerability in opac-patron-consent.pl.
|
||||
|
||||
## Bugfixes
|
||||
|
||||
### Acquisitions
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [39514](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39514) If one basket has uncertain prices, all baskets are displayed in red
|
||||
>This fixes the display of baskets in acquisitions so that only baskets with uncertain prices are shown in red. Previously, if one basket had an uncertain price, all the baskets in the page were shown in red, even those without uncertain prices, making it hard to know where to go to fix the price.
|
||||
|
||||
### Architecture, internals, and plumbing
|
||||
|
||||
#### Critical bugs fixed
|
||||
|
||||
- [38426](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=38426) Node.js v18 EOL around 25.05 release time
|
||||
- [40989](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40989) t/db_dependent/OAI/Server.t fails on Debian 13
|
||||
>This fixes OAI-PMH (tests and request) so that OAI-PMH responses work when Debian 13 (Trixie) is used as the operating system. Trailing slashes are stripped from requestURL for CGI.pm 4.68+ compatibility
|
||||
>
|
||||
>Technical details:
|
||||
>
|
||||
>CGI.pm 4.68 (shipped with Debian 13/Trixie) changed the behaviour of
|
||||
>self_url() to include a trailing slash when there's no path component.
|
||||
>
|
||||
>This 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.
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [41238](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41238) Pseudonymize statistic jobs don't update progress
|
||||
>This fixes the progress shown for pseudonymize statistics background jobs. The progress was shown in the list of jobs (Administration > Jobs > Manage jobs) as "0/1" instead of "1/1", even though the background job was finished.
|
||||
- [41404](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41404) No need to check related guarantor/guarantee charges when the limits are not set
|
||||
|
||||
### Circulation
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [40949](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40949) Bookings to collect shouldn't tell staff to check in items
|
||||
>This removes the sentence "Please retrieve them and check them in." from the bookings to collect page (Circulation > Holds and bookings > Bookings to collect). This is not required, as checking in items is not relevant for bookings.
|
||||
- [41345](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41345) Regression: Clicking the 'Ignore' button on hold found modal for already-waiting hold does not dismiss the modal (again)
|
||||
>This fixes a regression when checking in an item. Clicking the "Ignore" option in the dialog box, when an item already has a waiting status, just reloaded the dialog box. Clicking the "Ignore" option now closes the dialog box and works as expected.
|
||||
- [41352](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41352) Bookings to Collect Help does not take you to the correct place in the manual
|
||||
>This fixes the link to the help for the Circulation > Holds and bookings > Bookings to collect page - it now links to the correct place in the documentation, instead of the documentation home page.
|
||||
- [41451](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41451) Hold history search fails when itemtype column present
|
||||
>This fixes filtering on a patron's holds history when AllowHoldItemTypeSelection is enabled. This previously produced a 500 error when searching, but now works as expected.
|
||||
- [41456](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41456) Item type filter on the hold history view does not work correctly
|
||||
>This fixes the patron's holds history table in the staff interface. The search filter now works as expected - using the library name or library code and requested item type name or item type code (when AllowHoldItemTypeSelection is enabled) now work as expected, and the column filters for both of these now use dropdown lists.
|
||||
|
||||
### Hold requests
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [41432](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41432) Add prefetch to improve performance of holds page
|
||||
>This improves the performance of the holds request page for a record in the staff interface.
|
||||
|
||||
### I18N/L10N
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [40287](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40287) Fix untranslatable strings in more statistics wizards
|
||||
>This fixes and improves the acquisitions, patrons, catalog, and circulation statistics report wizard:
|
||||
>- Fixes some strings so that they are now translatable
|
||||
>- Improves the "Filtered on" information shown before the report results:
|
||||
> . the filtered on options selected in the report are now shown in a bulleted list and in bold
|
||||
> . descriptions are now shown instead of codes (for example, the library name instead of the library code)
|
||||
|
||||
**Sponsored by** *Athens County Public Libraries*
|
||||
|
||||
### Installation and upgrade (web-based installer)
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [40006](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40006) Upgrading install.pl shows code vs HTML
|
||||
>This fixes a database update (for bug 38436 in 24.11.00) that caused code to show in the browser instead of HTML output when running an upgrade.
|
||||
|
||||
### OPAC
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [40619](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40619) Remove OverDrive star ratings from the OPAC
|
||||
>This removes the code for OverDrive star ratings from OPAC pages, as OverDrive no longer supplies star ratings through their API.
|
||||
|
||||
**Sponsored by** *Athens County Public Libraries*
|
||||
|
||||
### Patrons
|
||||
|
||||
#### Critical bugs fixed
|
||||
|
||||
- [41145](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41145) Logging patron attributes logs even if there's no changes
|
||||
>This prevents misleading patron attribute modification logs, when a library batch imports patrons with the BorrowersLog system preference set to 'Log'. It now correctly only shows a log entry when a patron attribute value is changed.
|
||||
>
|
||||
>Example:
|
||||
>- Before the change: for an existing patron with a patron attribute of INSTID:1234, with a re-import the log shows { "attribute.INSTID" : { "after" : "1234", "before" : "" } }, even though there is no change to the patron attribute.
|
||||
>- After the change:
|
||||
> . No log entry is shown if there is no change to the patron attribute.
|
||||
> . If there is a change to the patron attribute (for example, changed to 5678 on a re-import), it is now correctly shown - { "attribute.INSTID" : { "after" : "5678", "before" : "1234" } }
|
||||
|
||||
**Sponsored by** *Auckland University of Technology*
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [39014](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39014) Storing a guarantee fails due to TrackLastPatronActivityTriggers "creating a patron"
|
||||
>This fixes an error when creating a patron that requires a guarantor, when this combination of settings is used:
|
||||
>- `TrackLastPatronActivityTriggers`: 'Creating a patron' is selected
|
||||
>- `ChildNeedsGuarantor`: is set to 'requires'
|
||||
>- The patron category is a 'Child' and 'Can be guarantee' is set to 'Yes'
|
||||
>
|
||||
>With these settings, this error message "The following fields are wrong. Please fix them." (without a list of fields) was incorrectly shown when creating a child patron, even though you had correctly added the guarantor information.
|
||||
|
||||
**Sponsored by** *Koha-Suomi Oy*
|
||||
- [41363](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41363) Don't hide patron category limitation warning behind icon
|
||||
>This moves the hint text for the warning on the Library management > Category field (when editing a patron record) from a tool tip on the warning icon to standard hint text under the input field, to make it more accessible
|
||||
>
|
||||
>Note: This warning only appears under certain circumstances (when a patron category is limited to a specific library, and you edit a patron when the library is set to another location).
|
||||
|
||||
**Sponsored by** *Athens County Public Libraries*
|
||||
|
||||
### Point of Sale
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [41408](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41408) POS Inline Editing Triggers Form Submission on Enter Key
|
||||
>Inline editing the cost or quantity fields on a point of sale transaction, then pressing enter, incorrectly submitted the form--instead of just updating the field.
|
||||
>
|
||||
>Now, the values in the fields are updated without submitting the form, and you can continue entering sale details and completing the transaction.
|
||||
|
||||
### REST API
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [40219](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40219) Welcome Email Sent on Failed Patron Registration via API
|
||||
>This fixes patron registrations using the API - a welcome email notice was sent even if there were validation failures.
|
||||
|
||||
### Reports
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [41292](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41292) Add "force_password_reset_when_set_by_staff" to the allowed column name list
|
||||
>This adds the force_password_reset_when_set_by_staff field in the categories table to the list of allowed password-related fields that can be used in SQL reports.
|
||||
>
|
||||
>Currently, this field is treated as containing sensitive password-related data and generates an error when creating a report that uses it.
|
||||
|
||||
### SIP2
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [40455](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40455) A patron information request fails when no currency is set
|
||||
>This fixes an error in SIP2 patron information responses (64) when no currency is set, which can be the case for new Koha instances.
|
||||
>
|
||||
>Example, if no currency is set:
|
||||
>- Before the fix, there is an 'undef' error:
|
||||
> ...
|
||||
> Trying 'patron_information'
|
||||
> SEND: 6300120251202 115652 AOCPL|AA42|ACterm1|
|
||||
> READ: undef
|
||||
>- After the fix, patron informatin is returned and there is no error:
|
||||
> ...
|
||||
> Trying 'patron_information'
|
||||
> SEND: 6300120251202 122545 AOCPL|AA42|ACterm1|
|
||||
> READ: 64 00120251202
|
||||
> 122546000000000000000000000000AOCPL|AA42|AE koha|BLY|BV0|CC5|PCS|
|
||||
> PIY|AFGreetings from Koha. |
|
||||
|
||||
### Searching - Elasticsearch
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [38345](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=38345) Restore support for OpenSearch
|
||||
>This fixes Koha so that OpenSearch now works, and is now covered again by continuous integration tests (these have been failing for some time in Jenkins).
|
||||
>
|
||||
>Note: See the test plan for guidance on how to start KTD successfully with OS1 and OS2 if you have issues.
|
||||
- [40980](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40980) Clicking a search facet without logging in may trigger a cud-login error
|
||||
>This fixes using facets in the OPAC for searching when not logged in, where Elasticsearch or OpenSearch is used as the search engine. In some circumstances, a 403 Forbidden Error was incorrectly generated (this is related to changes made in previous versions of Koha to improve form security).
|
||||
|
||||
### Serials
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [37796](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=37796) Generated issue has incorrect number in pattern when receiving
|
||||
>This fixes an issue with incorrect numbering patterns and the next expected issue shown, after a serial is received.
|
||||
>
|
||||
>Example of an incorrect numbering pattern before this fix:
|
||||
>- Monthly serial received for August 2025 (its status shows as 'Arrived' for August 2025).
|
||||
>- The next expected serial was then shown as August 2025 instead of September 2025 (its status was shown as 'Expected' with a incorrect numbering pattern of August 2025)
|
||||
|
||||
### Staff interface
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [41427](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41427) Terminology: branch should be library in FilterSearchResultsByLoggedInBranch
|
||||
>This fixes the terminology and improves the description for the FilterSearchResultsByLoggedInBranch system preference - branch should be library.
|
||||
|
||||
### System Administration
|
||||
|
||||
#### Critical bugs fixed
|
||||
|
||||
- [39482](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=39482) Link to edit OpacLibraryInfo from library edit page broken
|
||||
>This fixes editing and showing OpacLibraryInfo HTML customizations information in the staff interface and OPAC:
|
||||
>
|
||||
>1. Staff interface:
|
||||
> - When editing a library (Koha administration > Libraries > Actions > Edit)
|
||||
> . the links to edit OpacLibraryInfo entries for the OPAC information field are now correct (previously, they may have linked to an incorrect HTML customization)
|
||||
> . if there is more than one OpacLibraryInfo entry, all entries are now shown (only one entry was shown previously)
|
||||
> - When viewing library information (Koha administration > Libraries > Name > [click library name]), all the OpacLibraryInfo entries are now shown
|
||||
>
|
||||
>2. OPAC:
|
||||
> - All the entries for a library (including any 'All libraries' entries for OpacLibraryInfo) are now shown on the library information page (from the 'Libraries' link under the quick search bar)
|
||||
> - In a record's holdings table, the pop-up window when you click on the current library for an item now correctly shows all entries
|
||||
|
||||
**Sponsored by** *Athens County Public Libraries*
|
||||
|
||||
### Templates
|
||||
|
||||
#### Other bugs fixed
|
||||
|
||||
- [40567](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40567) Correct eslint errors in recalls.js
|
||||
>This fixes a few minor coding guideline errors in the JavaScript used on recalls pages (JS8: Follow guidelines set by ESLint). There are no changes to how the recalls pages work.
|
||||
|
||||
**Sponsored by** *Athens County Public Libraries*
|
||||
- [41339](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41339) Typo 'Too many checkout'
|
||||
>Changes "Too many checkout" to "Too many checkouts" message that shows in the log viewer when circulation overrides are logged.
|
||||
- [41348](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41348) Capitalization: "List Files" and others
|
||||
>This fixes the capitalization for messages used with FTP/SFTP file transfers (Administration > Additional parameters > File transports).
|
||||
>
|
||||
>Changes:
|
||||
>- "List Files" to "List files"
|
||||
>- "Change Directory" to "Change directory"
|
||||
- [41361](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41361) Incorrect markup in category code confirmation modal
|
||||
>This fixes the "Confirm expiration date" dialog box that is shown when changing an individual patron's category:
|
||||
>- The "No" option now works.
|
||||
>- It is now formatted using our standard Bootstrap 5 styles.
|
||||
|
||||
**Sponsored by** *Athens County Public Libraries*
|
||||
- [41395](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41395) Terminology: Target item cannot be reserved from other branches
|
||||
>Updates the terminology for two circulation-related messages (reserves to holds, branches to libraries):
|
||||
>
|
||||
>- "Target item cannot be reserved from other branches" to "Target item cannot be placed on hold from other libraries"
|
||||
>- "No reserves allowed" to "No holds allowed"
|
||||
- [41396](https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=41396) Capitalization: 'Transport Settings' and other
|
||||
>This fixes the capitalization for the section headings on the EDI account create and edit form - they are now in sentence case (Administration > Acquisition parameters > EDI accounts):
|
||||
>
|
||||
>- Basic Information => Basic information
|
||||
>- Transport Settings => Transport settings
|
||||
>- Message Types => Message types
|
||||
>- Functional Switches => Functional switches
|
||||
|
||||
**Sponsored by** *Athens County Public Libraries*
|
||||
|
||||
## Documentation
|
||||
|
||||
The Koha manual is maintained in Sphinx. The home page for Koha
|
||||
documentation is
|
||||
|
||||
- [Koha Documentation](https://koha-community.org/documentation/)
|
||||
As of the date of these release notes, the Koha manual is available in the following languages:
|
||||
|
||||
- [English (USA)](https://koha-community.org/manual/25.11/en/html/)
|
||||
- [French](https://koha-community.org/manual/25.11/fr/html/) (74%)
|
||||
- [German](https://koha-community.org/manual/25.11/de/html/) (90%)
|
||||
- [Greek](https://koha-community.org/manual/25.11/el/html/) (94%)
|
||||
- [Hindi](https://koha-community.org/manual/25.11/hi/html/) (64%)
|
||||
|
||||
The Git repository for the Koha manual can be found at
|
||||
|
||||
- [Koha Git Repository](https://gitlab.com/koha-community/koha-manual)
|
||||
|
||||
## Translations
|
||||
|
||||
Complete or near-complete translations of the OPAC and staff
|
||||
interface are available in this release for the following languages:
|
||||
<div style="column-count: 2;">
|
||||
|
||||
- Arabic (ar_ARAB) (90%)
|
||||
- Armenian (hy_ARMN) (100%)
|
||||
- Bulgarian (bg_CYRL) (100%)
|
||||
- Chinese (Simplified Han script) (82%)
|
||||
- Chinese (Traditional Han script) (95%)
|
||||
- Czech (65%)
|
||||
- Dutch (85%)
|
||||
- English (100%)
|
||||
- English (New Zealand) (60%)
|
||||
- English (USA)
|
||||
- Finnish (99%)
|
||||
- French (99%)
|
||||
- French (Canada) (97%)
|
||||
- German (99%)
|
||||
- Greek (64%)
|
||||
- Hindi (93%)
|
||||
- Italian (79%)
|
||||
- Norwegian Bokmål (69%)
|
||||
- Persian (fa_ARAB) (91%)
|
||||
- Polish (99%)
|
||||
- Portuguese (Brazil) (99%)
|
||||
- Portuguese (Portugal) (86%)
|
||||
- Russian (91%)
|
||||
- Slovak (57%)
|
||||
- Spanish (96%)
|
||||
- Swedish (88%)
|
||||
- Telugu (64%)
|
||||
- Turkish (79%)
|
||||
- Ukrainian (73%)
|
||||
- Western Armenian (hyw_ARMN) (59%)
|
||||
</div>
|
||||
|
||||
Partial translations are available for various other languages.
|
||||
|
||||
The Koha team welcomes additional translations; please see
|
||||
|
||||
- [Koha Translation Info](https://wiki.koha-community.org/wiki/Translating_Koha)
|
||||
|
||||
For information about translating Koha, and join the koha-translate
|
||||
list to volunteer:
|
||||
|
||||
- [Koha Translate List](https://lists.koha-community.org/cgi-bin/mailman/listinfo/koha-translate)
|
||||
|
||||
The most up-to-date translations can be found at:
|
||||
|
||||
- [Koha Translation](https://translate.koha-community.org/)
|
||||
|
||||
## Release Team
|
||||
|
||||
The release team for Koha 25.11.01 is
|
||||
|
||||
|
||||
- Release Manager: Lucas Gass
|
||||
|
||||
- QA Manager: Martin Renvoize
|
||||
|
||||
- QA Team:
|
||||
- Marcel de Rooy
|
||||
- Martin Renvoize
|
||||
- Jonathan Druart
|
||||
- Laura Escamilla
|
||||
- Lucas Gass
|
||||
- Tomás Cohen Arazi
|
||||
- Lisette Scheer
|
||||
- Nick Clemens
|
||||
- Paul Derscheid
|
||||
- Emily Lamancusa
|
||||
- David Cook
|
||||
- Matt Blenkinsop
|
||||
- Andrew Fuerste-Henry
|
||||
- Brendan Lawlor
|
||||
- Pedro Amorim
|
||||
- Kyle M Hall
|
||||
- Aleisha Amohia
|
||||
- David Nind
|
||||
- Baptiste Wojtkowski
|
||||
- Jan Kissig
|
||||
- Katrin Fischer
|
||||
- Thomas Klausner
|
||||
- Julian Maurice
|
||||
- Owen Leonard
|
||||
|
||||
- Documentation Manager: David Nind
|
||||
|
||||
- Documentation Team:
|
||||
- Philip Orr
|
||||
- Aude Charillon
|
||||
- Caroline Cyr La Rose
|
||||
|
||||
- Translation Manager: Jonathan Druart
|
||||
|
||||
|
||||
- Wiki curators:
|
||||
- George Williams
|
||||
- Thomas Dukleth
|
||||
|
||||
- Release Maintainers:
|
||||
- 25.11 -- Jacob O'Mara
|
||||
- 25.05 -- Laura Escamilla
|
||||
- 24.11 -- Fridolin Somers
|
||||
- 22.11 -- Wainui Witika-Park (Catalyst IT)
|
||||
|
||||
- Release Maintainer assistants:
|
||||
- 25.11 -- Chloé Zermatten
|
||||
- 24.11 -- Baptiste Wojtkowski
|
||||
- 22.11 -- Alex Buckley & Aleisha Amohia
|
||||
|
||||
## Credits
|
||||
|
||||
We thank the following libraries, companies, and other institutions who are known to have sponsored
|
||||
new features in Koha 25.11.01
|
||||
<div style="column-count: 2;">
|
||||
|
||||
- Athens County Public Libraries
|
||||
- Auckland University of Technology
|
||||
- [Koha-Suomi Oy](https://koha-suomi.fi)
|
||||
</div>
|
||||
|
||||
We thank the following individuals who contributed patches to Koha 25.11.01
|
||||
<div style="column-count: 2;">
|
||||
|
||||
- Pedro Amorim (1)
|
||||
- Tomás Cohen Arazi (2)
|
||||
- Alex Buckley (2)
|
||||
- Nick Clemens (6)
|
||||
- David Cook (2)
|
||||
- Paul Derscheid (2)
|
||||
- Jonathan Druart (7)
|
||||
- Andrew Fuerste-Henry (2)
|
||||
- Lucas Gass (3)
|
||||
- Jan Kissig (1)
|
||||
- Owen Leonard (10)
|
||||
- David Nind (2)
|
||||
- Jacob O'Mara (4)
|
||||
- Martin Renvoize (3)
|
||||
- Marcel de Rooy (1)
|
||||
- Caroline Cyr La Rose (1)
|
||||
- Lisette Scheer (1)
|
||||
- Leo Stoyanov (1)
|
||||
- Emmi Takkinen (1)
|
||||
- Lari Taskula (1)
|
||||
</div>
|
||||
|
||||
We thank the following libraries, companies, and other institutions who contributed
|
||||
patches to Koha 25.11.01
|
||||
<div style="column-count: 2;">
|
||||
|
||||
- Athens County Public Libraries (10)
|
||||
- [ByWater Solutions](https://bywatersolutions.com) (13)
|
||||
- [Catalyst](https://www.catalyst.net.nz/products/library-management-koha) (2)
|
||||
- David Nind (2)
|
||||
- [Hypernova Oy](https://www.hypernova.fi) (1)
|
||||
- Koha Community Developers (7)
|
||||
- [Koha-Suomi Oy](https://koha-suomi.fi) (1)
|
||||
- [LMSCloud](https://www.lmscloud.de) (2)
|
||||
- [Open Fifth](https://openfifth.co.uk/) (8)
|
||||
- [Prosentient Systems](https://www.prosentient.com.au) (2)
|
||||
- Rijksmuseum, Netherlands (1)
|
||||
- [Solutions inLibro inc](https://inlibro.com) (1)
|
||||
- [Theke Solutions](https://theke.io) (2)
|
||||
- Wildau University of Technology (1)
|
||||
</div>
|
||||
|
||||
We also especially thank the following individuals who tested patches
|
||||
for Koha
|
||||
<div style="column-count: 2;">
|
||||
|
||||
- Christopher Brannon (1)
|
||||
- Aude Charillon (1)
|
||||
- Jonathan Druart (3)
|
||||
- Magnus Enger (1)
|
||||
- Laura Escamilla (8)
|
||||
- Lucas Gass (42)
|
||||
- Victor Grousset (1)
|
||||
- Owen Leonard (1)
|
||||
- David Nind (29)
|
||||
- Jacob O'Mara (49)
|
||||
- Wesley Owen (2)
|
||||
- Martin Renvoize (12)
|
||||
- Marcel de Rooy (11)
|
||||
- Samuel (1)
|
||||
- Lisette Scheer (3)
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
We regret any omissions. If a contributor has been inadvertently missed,
|
||||
please send a patch against these release notes to koha-devel@lists.koha-community.org.
|
||||
|
||||
## Revision control notes
|
||||
|
||||
The Koha project uses Git for version control. The current development
|
||||
version of Koha can be retrieved by checking out the main branch of:
|
||||
|
||||
- [Koha Git Repository](https://git.koha-community.org/koha-community/koha)
|
||||
|
||||
The branch for this version of Koha and future bugfixes in this release
|
||||
line is 25.11.x.
|
||||
|
||||
## Bugs and feature requests
|
||||
|
||||
Bug reports and feature requests can be filed at the Koha bug
|
||||
tracker at:
|
||||
|
||||
- [Koha Bugzilla](https://bugs.koha-community.org)
|
||||
|
||||
He rau ringa e oti ai.
|
||||
(Many hands finish the work)
|
||||
|
||||
Autogenerated release notes updated last on 28 Jan 2026 11:17:10.
|
||||
|
|
@ -718,8 +718,8 @@ if ( not $viewallitems and $items->count > $max_items_to_display ) {
|
|||
$library_info->{ $item->holdingbranch } = $opac_info_holding;
|
||||
$opac_info_home = $library_info->{ $item->homebranch } // $item->home_branch->opac_info( { lang => $lang } );
|
||||
$library_info->{ $item->homebranch } = $opac_info_home;
|
||||
$item_info->{holding_library_info} = $opac_info_holding->content if $opac_info_holding;
|
||||
$item_info->{home_library_info} = $opac_info_home->content if $opac_info_home;
|
||||
$item_info->{holding_library_info} = $opac_info_holding if $opac_info_holding;
|
||||
$item_info->{home_library_info} = $opac_info_home if $opac_info_home;
|
||||
|
||||
# We only need to check if we haven't determined holds can be placed
|
||||
# and if we don't have patron, we have already decided
|
||||
|
|
|
|||
|
|
@ -48,24 +48,27 @@ foreach my $consent_type ( sort keys %$consent_types ) {
|
|||
push @consents, $patron->consent($consent_type);
|
||||
}
|
||||
|
||||
# Handle saves here
|
||||
my $needs_redirect;
|
||||
foreach my $consent (@consents) {
|
||||
my $check = $vars->{ "check_" . $consent->type };
|
||||
next if !defined($check); # no choice made
|
||||
$needs_redirect = 1
|
||||
if $consent->type eq q/GDPR_PROCESSING/
|
||||
&& !$check
|
||||
&& C4::Context->preference('PrivacyPolicyConsent') eq 'Enforced';
|
||||
next if $consent->given_on && $check || $consent->refused_on && !$check;
|
||||
|
||||
# No update if no consent change
|
||||
$consent->set(
|
||||
{
|
||||
given_on => $check ? dt_from_string() : undef,
|
||||
refused_on => $check ? undef : dt_from_string(),
|
||||
}
|
||||
)->store;
|
||||
# Handle saves here
|
||||
if ( $op && $op eq 'cud-save' ) {
|
||||
foreach my $consent (@consents) {
|
||||
my $check = $vars->{ "check_" . $consent->type };
|
||||
next if !defined($check); # no choice made
|
||||
$needs_redirect = 1
|
||||
if $consent->type eq q/GDPR_PROCESSING/
|
||||
&& !$check
|
||||
&& C4::Context->preference('PrivacyPolicyConsent') eq 'Enforced';
|
||||
next if $consent->given_on && $check || $consent->refused_on && !$check;
|
||||
|
||||
# No update if no consent change
|
||||
$consent->set(
|
||||
{
|
||||
given_on => $check ? dt_from_string() : undef,
|
||||
refused_on => $check ? undef : dt_from_string(),
|
||||
}
|
||||
)->store;
|
||||
}
|
||||
}
|
||||
|
||||
# If user refused GDPR consent and we enforce GDPR, logout (when saving)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@
|
|||
},
|
||||
"resolutions": {
|
||||
"minimatch": "^3.0.2",
|
||||
"lodash": "^4.17.12"
|
||||
"lodash": "^4.17.12",
|
||||
"@types/express": "^4.17.13"
|
||||
},
|
||||
"author": "",
|
||||
"license": "GPL-3.0",
|
||||
|
|
|
|||
|
|
@ -193,22 +193,25 @@ sub calculate {
|
|||
foreach my $i ( 0 .. scalar @$filters ) {
|
||||
my %cell;
|
||||
if ( @$filters[$i] ) {
|
||||
if ( $i == 0 ) { $cell{crit} = "Cat code"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 1 ) { $cell{crit} = "ZIP/Postal code"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 2 ) { $cell{crit} = "Branch code"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 3 || $i == 4 ) { $cell{crit} = "Date of birth"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 5 ) { $cell{crit} = "Sex"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 6 ) { $cell{crit} = "Sort1"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 7 ) { $cell{crit} = "Sort2"; $cell{filter} = @$filters[$i]; }
|
||||
else { $cell{crit} = "Unknown"; }
|
||||
if ( $i == 0 ) { $cell{crit} = "Cat code"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 1 ) { $cell{crit} = "ZIP/Postal code"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 2 ) { $cell{crit} = "Branch code"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 3 ) { $cell{crit} = "Date of birth1"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 4 ) { $cell{crit} = "Date of birth2"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 5 ) { $cell{crit} = "Sex"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 6 ) { $cell{crit} = "Sort1"; $cell{filter} = @$filters[$i]; }
|
||||
elsif ( $i == 7 ) { $cell{crit} = "Sort2"; $cell{filter} = @$filters[$i]; }
|
||||
else { $cell{crit} = "Unknown"; }
|
||||
|
||||
push @loopfilter, \%cell;
|
||||
}
|
||||
}
|
||||
foreach my $type ( keys %$attr_filters ) {
|
||||
if ( $attr_filters->{$type} ) {
|
||||
my $patron_attribute_type = Koha::Patron::Attribute::Types->find($type);
|
||||
push @loopfilter, {
|
||||
crit => "Attribute $type",
|
||||
crit => "PA_CLASS",
|
||||
label => $patron_attribute_type->description,
|
||||
filter => $attr_filters->{$type}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,10 +230,18 @@ sub calculate {
|
|||
|
||||
push @loopfilter, \%cell;
|
||||
}
|
||||
foreach ( keys %$attribute_filters ) {
|
||||
next unless $attribute_filters->{$_};
|
||||
push @loopfilter, { crit => "$_ =", filter => $attribute_filters->{$_} };
|
||||
|
||||
foreach my $type ( keys %$attribute_filters ) {
|
||||
if ( $attribute_filters->{$type} ) {
|
||||
my $patron_attribute_type = Koha::Patron::Attribute::Types->find($type);
|
||||
push @loopfilter, {
|
||||
crit => "PA_CLASS",
|
||||
label => $patron_attribute_type->description,
|
||||
filter => $attribute_filters->{$type}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
push @loopfilter, { crit => "Event", filter => $type };
|
||||
push @loopfilter, { crit => "Display by", filter => $dsp } if ($dsp);
|
||||
push @loopfilter, { crit => "Select Day", filter => $daysel } if ($daysel);
|
||||
|
|
|
|||
|
|
@ -683,8 +683,10 @@ if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold )
|
|||
)->unblessed
|
||||
}
|
||||
};
|
||||
my @reserves =
|
||||
Koha::Holds->search( { biblionumber => $biblionumber }, { order_by => 'priority' } )->as_list;
|
||||
my @reserves = Koha::Holds->search(
|
||||
{ 'me.biblionumber' => $biblionumber },
|
||||
{ prefetch => [ 'borrowernumber', 'itemnumber' ], order_by => 'priority' }
|
||||
)->as_list;
|
||||
foreach my $res (
|
||||
sort {
|
||||
my $a_found = $a->found() || '';
|
||||
|
|
|
|||
|
|
@ -82,14 +82,20 @@ if ( $op eq 'cud-gennext' && @subscriptionid ) {
|
|||
my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern( $subscription->{numberpattern} );
|
||||
my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency( $subscription->{periodicity} );
|
||||
my $expected = GetNextExpected($subscriptionid);
|
||||
my (
|
||||
$newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
|
||||
$newinnerloop1, $newinnerloop2, $newinnerloop3
|
||||
) = GetNextSeq( $subscription, $pattern, $frequency, $expected->{publisheddate}, $count_forward );
|
||||
|
||||
## We generate the next publication date
|
||||
my $nextpublisheddate = GetNextDate( $subscription, $expected->{publisheddate}, $frequency, 1 );
|
||||
my $planneddate = $date_received_today ? dt_from_string : $nextpublisheddate;
|
||||
|
||||
my (
|
||||
$newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
|
||||
$newinnerloop1, $newinnerloop2, $newinnerloop3
|
||||
)
|
||||
= GetNextSeq(
|
||||
$subscription, $pattern, $frequency, $expected->{publisheddate}, $nextpublisheddate,
|
||||
$count_forward
|
||||
);
|
||||
|
||||
my $planneddate = $date_received_today ? dt_from_string : $nextpublisheddate;
|
||||
## Creating the new issue
|
||||
NewIssue(
|
||||
$newserialseq, $subscriptionid, $subscription->{'biblionumber'},
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ while ( $i < 1000 ) {
|
|||
(
|
||||
$calculated, $subscription{'lastvalue1'}, $subscription{'lastvalue2'}, $subscription{'lastvalue3'},
|
||||
$subscription{'innerloop1'}, $subscription{'innerloop2'}, $subscription{'innerloop3'}
|
||||
) = GetNextSeq( \%subscription, \%pattern, $frequency, $date );
|
||||
) = GetNextSeq( \%subscription, \%pattern, $frequency, $date, $date );
|
||||
$issuenumber++;
|
||||
$line{'number'} = $calculated;
|
||||
$line{'issuenumber'} = $issuenumber;
|
||||
|
|
|
|||
|
|
@ -103,6 +103,18 @@ my $filter_archived = $input->param('filter_archived') || 0;
|
|||
my $new_itemtype = $input->param('suggestion_itemtype');
|
||||
my $sugg_managedby = $input->param('suggestion_managedby');
|
||||
|
||||
#NOTE: Validate displayby as it gets passed to sensitive functions
|
||||
my %valid_displayby = (
|
||||
STATUS => 1,
|
||||
branchcode => 1,
|
||||
itemtype => 1,
|
||||
managedby => 1,
|
||||
acceptedby => 1,
|
||||
);
|
||||
if ( !$valid_displayby{$displayby} ) {
|
||||
$displayby = '';
|
||||
}
|
||||
|
||||
my $reasonsloop = GetAuthorisedValues("SUGGEST");
|
||||
|
||||
my $suggestion_ref = { $input->Vars };
|
||||
|
|
|
|||
|
|
@ -259,6 +259,6 @@ sub _next_seq {
|
|||
$seq, $subscription->{lastvalue1}, $subscription->{lastvalue2},
|
||||
$subscription->{lastvalue3}, $subscription->{innerloop1},
|
||||
$subscription->{innerloop2}, $subscription->{innerloop3}
|
||||
) = GetNextSeq( $subscription, $pattern, undef, undef, $count_forward );
|
||||
) = GetNextSeq( $subscription, $pattern, undef, undef, undef, $count_forward );
|
||||
return $seq;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
describe("Suggestions", () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.title().should("eq", "Koha staff interface");
|
||||
});
|
||||
|
||||
it("Should sanitize displayby", () => {
|
||||
cy.visit("/cgi-bin/koha/suggestion/suggestion.pl?displayby=foo");
|
||||
// Do not 500 and default to "STATUS" if displayby is not valid
|
||||
cy.get("#displayby").should("have.value", "STATUS");
|
||||
});
|
||||
});
|
||||
|
|
@ -733,7 +733,8 @@ const insertObject = async ({ type, object, baseUrl, authHeader }) => {
|
|||
suspended,
|
||||
suspended_until,
|
||||
non_priority,
|
||||
item_type,
|
||||
item_type_id,
|
||||
_strings,
|
||||
hold_group_id,
|
||||
item_level,
|
||||
cancellation_requested,
|
||||
|
|
|
|||
|
|
@ -7667,7 +7667,7 @@ subtest 'NoRefundOnLostFinesPaidAge' => sub {
|
|||
};
|
||||
|
||||
subtest 'ChildNeedsGuarantor' => sub {
|
||||
plan tests => 18;
|
||||
plan tests => 12;
|
||||
|
||||
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
|
||||
my $library = $builder->build_object( { class => 'Koha::Libraries' } );
|
||||
|
|
@ -7745,47 +7745,6 @@ subtest 'ChildNeedsGuarantor' => sub {
|
|||
library => $library->branchcode,
|
||||
}
|
||||
);
|
||||
warnings_like {
|
||||
AddIssue( $child_patron, $item->barcode, undef );
|
||||
}
|
||||
qr/Problem updating lastseen/,
|
||||
"AddIssue generates a warning when Child type patron is missing a guarantor and ChildsNeedsGuarantor";
|
||||
warnings_like {
|
||||
AddRenewal(
|
||||
{
|
||||
borrowernumber => $child_patron->borrowernumber, itemnumber => $item->itemnumber,
|
||||
branch => $library->branchcode
|
||||
}
|
||||
);
|
||||
}
|
||||
qr/Problem updating lastseen/,
|
||||
"AddRenewal generates a warning when Child type patron is missing a guarantor and ChildNeedsGuarantor";
|
||||
warnings_like {
|
||||
AddReturn( $item->barcode, $library->branchcode, undef, undef );
|
||||
}
|
||||
qr/Problem updating lastseen/,
|
||||
"AddReturn generates a warning when Child type patron is missing a guarantor and ChildNeedsGuarantor";
|
||||
|
||||
warnings_like {
|
||||
AddIssue( $guarantee_patron, $item->barcode, undef );
|
||||
}
|
||||
qr/Problem updating lastseen/,
|
||||
"AddIssue generates a warning when can_be_guarantee type patron is missing a guarantor and ChildsNeedsGuarantor";
|
||||
warnings_like {
|
||||
AddRenewal(
|
||||
{
|
||||
borrowernumber => $guarantee_patron->borrowernumber, itemnumber => $item->itemnumber,
|
||||
branch => $library->branchcode
|
||||
}
|
||||
);
|
||||
}
|
||||
qr/Problem updating lastseen/,
|
||||
"AddRenewal generates a warning when can_be_guarantee type patron is missing a guarantor and ChildNeedsGuarantor";
|
||||
warnings_like {
|
||||
AddReturn( $item->barcode, $library->branchcode, undef, undef );
|
||||
}
|
||||
qr/Problem updating lastseen/,
|
||||
"AddReturn generates a warning when can_be_guarantee type patron is missing a guarantor and ChildNeedsGuarantor";
|
||||
|
||||
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
|
||||
warnings_like {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ subtest 'enqueue() tests' => sub {
|
|||
|
||||
subtest 'process() tests' => sub {
|
||||
|
||||
plan tests => 3;
|
||||
plan tests => 4;
|
||||
|
||||
$schema->storage->txn_begin;
|
||||
|
||||
|
|
@ -103,7 +103,8 @@ subtest 'process() tests' => sub {
|
|||
is( $statistic->{datetime}, $pt_after->last->datetime, "'datetime' column preserved" );
|
||||
|
||||
$job->discard_changes;
|
||||
is( $job->data, '{"data":""}', "Job data cleared after pseudonymization" );
|
||||
is( $job->data, '{"data":""}', "Job data cleared after pseudonymization" );
|
||||
is( $job->progress, 1, "Job progress is updated" );
|
||||
|
||||
$schema->storage->txn_rollback;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
use Modern::Perl;
|
||||
|
||||
use Test::NoWarnings;
|
||||
use Test::More tests => 20;
|
||||
use Test::More tests => 21;
|
||||
|
||||
use Test::Exception;
|
||||
use Test::MockModule;
|
||||
|
|
@ -92,6 +92,27 @@ subtest 'biblio() tests' => sub {
|
|||
$schema->storage->txn_rollback;
|
||||
};
|
||||
|
||||
subtest 'item_type() tests' => sub {
|
||||
|
||||
plan tests => 2;
|
||||
|
||||
$schema->storage->txn_begin;
|
||||
|
||||
my $hold = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Holds',
|
||||
value => { itemtype => undef },
|
||||
}
|
||||
);
|
||||
|
||||
is( $hold->item_type, undef, 'Koha::Hold->item_type returns undef if no item type selected' );
|
||||
my $item_type = $builder->build_object( { class => "Koha::ItemTypes" } );
|
||||
$hold->itemtype( $item_type->itemtype )->store;
|
||||
is( $hold->item_type->itemtype, $item_type->itemtype, 'Koha::Hold->item_type returns the correct item type' );
|
||||
|
||||
$schema->storage->txn_rollback;
|
||||
};
|
||||
|
||||
subtest 'pickup_library/branch tests' => sub {
|
||||
|
||||
plan tests => 1;
|
||||
|
|
@ -1207,7 +1228,7 @@ subtest 'change_type() tests' => sub {
|
|||
|
||||
subtest 'strings_map() tests' => sub {
|
||||
|
||||
plan tests => 3;
|
||||
plan tests => 4;
|
||||
|
||||
$schema->txn_begin;
|
||||
|
||||
|
|
@ -1233,7 +1254,8 @@ subtest 'strings_map() tests' => sub {
|
|||
is_deeply(
|
||||
$strings_map,
|
||||
{
|
||||
pickup_library_id => { str => $library->branchname, type => 'library' },
|
||||
pickup_library_id => { str => $library->branchname, type => 'library' },
|
||||
item_type_id => { str => $hold->item_type->description, type => 'item_type' },
|
||||
cancellation_reason => { str => $av->lib, type => 'av', category => 'HOLD_CANCELLATION' },
|
||||
},
|
||||
'Strings map is correct'
|
||||
|
|
@ -1243,7 +1265,8 @@ subtest 'strings_map() tests' => sub {
|
|||
is_deeply(
|
||||
$strings_map,
|
||||
{
|
||||
pickup_library_id => { str => $library->branchname, type => 'library' },
|
||||
pickup_library_id => { str => $library->branchname, type => 'library' },
|
||||
item_type_id => { str => $hold->item_type->description, type => 'item_type' },
|
||||
cancellation_reason => { str => $av->lib_opac, type => 'av', category => 'HOLD_CANCELLATION' },
|
||||
},
|
||||
'Strings map is correct (OPAC)'
|
||||
|
|
@ -1255,12 +1278,24 @@ subtest 'strings_map() tests' => sub {
|
|||
is_deeply(
|
||||
$strings_map,
|
||||
{
|
||||
pickup_library_id => { str => $library->branchname, type => 'library' },
|
||||
pickup_library_id => { str => $library->branchname, type => 'library' },
|
||||
item_type_id => { str => $hold->item_type->description, type => 'item_type' },
|
||||
cancellation_reason => { str => $hold->cancellation_reason, type => 'av', category => 'HOLD_CANCELLATION' },
|
||||
},
|
||||
'Strings map shows the cancellation_value when AV not present'
|
||||
);
|
||||
|
||||
$hold->itemtype(undef)->store;
|
||||
$strings_map = $hold->strings_map( { public => 1 } );
|
||||
is_deeply(
|
||||
$strings_map,
|
||||
{
|
||||
pickup_library_id => { str => $library->branchname, type => 'library' },
|
||||
cancellation_reason => { str => $hold->cancellation_reason, type => 'av', category => 'HOLD_CANCELLATION' },
|
||||
},
|
||||
'Strings map does not show item_type_id if none selected'
|
||||
);
|
||||
|
||||
$schema->txn_rollback;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -159,16 +159,17 @@ subtest 'opac_info tests' => sub {
|
|||
);
|
||||
|
||||
# Start testing
|
||||
is( $library01->opac_info->content, '2', 'specific library, default language' );
|
||||
is( $library01->opac_info( { lang => 'nl-NL' } )->content, '3', 'specific library, specific language' );
|
||||
is( $library01->opac_info( { lang => 'nl-BE' } )->content, '2', 'specific library, unknown language' );
|
||||
is( $library02->opac_info->content, '1', 'unknown library, default language' );
|
||||
is( $library02->opac_info( { lang => 'fr-FR' } )->content, '4', 'unknown library, specific language' );
|
||||
is( $library02->opac_info( { lang => 'de-DE' } )->content, '1', 'unknown library, unknown language' );
|
||||
ok( $library01->opac_info, 'specific library, default language' );
|
||||
ok( $library01->opac_info( { lang => 'nl-NL' } ), 'specific library, specific language' );
|
||||
ok( $library01->opac_info( { lang => 'nl-BE' } ), 'specific library, unknown language' );
|
||||
ok( $library02->opac_info, 'unknown library, default language' );
|
||||
ok( $library02->opac_info( { lang => 'fr-FR' } ), 'unknown library, specific language' );
|
||||
ok( $library02->opac_info( { lang => 'de-DE' } ), 'unknown library, unknown language' );
|
||||
$html01->delete;
|
||||
is( $library02->opac_info, undef, 'unknown library, default language (after removing html01)' );
|
||||
is(
|
||||
$library02->opac_info( { lang => 'de-DE' } ), undef,
|
||||
|
||||
ok( $library02->opac_info, 'unknown library, default language (after removing html01)' );
|
||||
ok(
|
||||
$library02->opac_info( { lang => 'de-DE' } ),
|
||||
'unknown library, unknown language (after removing html01)'
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
use Modern::Perl;
|
||||
|
||||
use Test::More tests => 45;
|
||||
use Test::More tests => 46;
|
||||
use Test::NoWarnings;
|
||||
use Test::Exception;
|
||||
use Test::Warn;
|
||||
|
|
@ -2624,93 +2624,78 @@ subtest 'get_lists_with_patron() tests' => sub {
|
|||
$schema->storage->txn_rollback;
|
||||
};
|
||||
|
||||
subtest 'guarantor requirements tests' => sub {
|
||||
subtest 'can_be_guaranteed_by() tests' => sub {
|
||||
|
||||
plan tests => 6;
|
||||
plan tests => 5;
|
||||
|
||||
$schema->storage->txn_begin;
|
||||
|
||||
my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
|
||||
my $child_category =
|
||||
$builder->build( { source => 'Category', value => { category_type => 'C', can_be_guarantee => 1 } } )
|
||||
->{categorycode};
|
||||
my $patron_category =
|
||||
$builder->build( { source => 'Category', value => { category_type => 'A', can_be_guarantee => 0 } } )
|
||||
->{categorycode};
|
||||
|
||||
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
|
||||
my $guarantee_category =
|
||||
$builder->build(
|
||||
{ source => 'Category', value => { categorycode => "GUARANTEE", category_type => 'C', can_be_guarantee => 1 } }
|
||||
);
|
||||
my $guarantor_category =
|
||||
$builder->build(
|
||||
{ source => 'Category', value => { categorycode => "GUARANTOR", category_type => 'A', can_be_guarantee => 0 } }
|
||||
);
|
||||
|
||||
my $child = Koha::Patron->new(
|
||||
{
|
||||
branchcode => $branchcode,
|
||||
categorycode => $child_category,
|
||||
categorycode => $guarantee_category->{categorycode},
|
||||
contactname => ''
|
||||
}
|
||||
);
|
||||
$child->store();
|
||||
|
||||
ok(
|
||||
Koha::Patrons->find( $child->id ),
|
||||
'Child patron can be stored without guarantor when ChildNeedsGuarantor is off.'
|
||||
);
|
||||
|
||||
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 1 );
|
||||
|
||||
my $child2 = Koha::Patron->new(
|
||||
{
|
||||
branchcode => $branchcode,
|
||||
categorycode => $child_category,
|
||||
contactname => ''
|
||||
}
|
||||
);
|
||||
my $child3 = $builder->build_object(
|
||||
my $child2 = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Patrons',
|
||||
value => { categorycode => $child_category }
|
||||
value => { categorycode => $guarantee_category->{categorycode} }
|
||||
}
|
||||
);
|
||||
my $patron = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Patrons',
|
||||
value => { categorycode => $patron_category }
|
||||
value => { categorycode => $guarantor_category->{categorycode} }
|
||||
}
|
||||
);
|
||||
my $patron2 = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Patrons',
|
||||
value => { categorycode => $guarantor_category->{categorycode} }
|
||||
}
|
||||
);
|
||||
|
||||
throws_ok { $child2->store(); }
|
||||
my @guarantors;
|
||||
my $contactname = "";
|
||||
my $category = Koha::Patron::Categories->find( $guarantee_category->{categorycode} );
|
||||
|
||||
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
|
||||
|
||||
lives_ok { $child->can_be_guaranteed_by( \@guarantors ); }
|
||||
'Validation passes when ChildNeedsGuarantor syspref is disabled';
|
||||
|
||||
t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 1 );
|
||||
|
||||
throws_ok { $child->can_be_guaranteed_by( \@guarantors ); }
|
||||
'Koha::Exceptions::Patron::Relationship::NoGuarantor',
|
||||
'Exception thrown when guarantor is required but not provided.';
|
||||
|
||||
my @guarantors = ( $patron, $child3 );
|
||||
throws_ok { $child2->store( { guarantors => \@guarantors } ); }
|
||||
@guarantors = ( $patron, $child2 );
|
||||
throws_ok { $child->can_be_guaranteed_by( \@guarantors ); }
|
||||
'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
|
||||
'Exception thrown when child patron is added as guarantor.';
|
||||
|
||||
#test ModMember
|
||||
@guarantors = ($patron);
|
||||
$child2->store( { guarantors => \@guarantors } )->discard_changes();
|
||||
|
||||
t::lib::Mocks::mock_preference( 'borrowerRelationship', '' );
|
||||
|
||||
my $relationship = Koha::Patron::Relationship->new(
|
||||
{
|
||||
guarantor_id => $patron->borrowernumber,
|
||||
guarantee_id => $child2->borrowernumber,
|
||||
relationship => ''
|
||||
}
|
||||
);
|
||||
$relationship->store();
|
||||
|
||||
ok( $child2->store(), 'Child patron can be modified and stored when guarantor is stored' );
|
||||
|
||||
@guarantors = ($child3);
|
||||
throws_ok { $child2->store( { guarantors => \@guarantors } ); }
|
||||
t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
|
||||
$child2->add_guarantor( { guarantor_id => $patron->id, relationship => 'parent' } );
|
||||
@guarantors = ($patron2);
|
||||
throws_ok { $patron->can_be_guaranteed_by( \@guarantors ); }
|
||||
'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
|
||||
'Exception thrown when child patron is modified and child patron is added as guarantor.';
|
||||
'Exception thrown when trying to add a guarantor to a guarantor.';
|
||||
|
||||
$relationship->delete;
|
||||
throws_ok { $child2->store(); }
|
||||
'Koha::Exceptions::Patron::Relationship::NoGuarantor',
|
||||
'Exception thrown when guarantor is deleted.';
|
||||
@guarantors = ($patron);
|
||||
lives_ok { $child->can_be_guaranteed_by( \@guarantors ); }
|
||||
'Validation passes when valid guarantors are passed in array';
|
||||
|
||||
$schema->storage->txn_rollback;
|
||||
};
|
||||
|
|
@ -2952,6 +2937,131 @@ subtest 'is_patron_inside_charge_limits() tests' => sub {
|
|||
$schema->storage->txn_rollback;
|
||||
};
|
||||
|
||||
subtest 'is_patron_inside_charge_limits - edge cases for empty string and zero' => sub {
|
||||
plan tests => 14;
|
||||
|
||||
$schema->storage->txn_begin;
|
||||
|
||||
my $patron_category = $builder->build(
|
||||
{
|
||||
source => 'Category',
|
||||
value => {
|
||||
categorycode => 'TEST_CAT',
|
||||
category_type => 'P',
|
||||
enrolmentfee => 0,
|
||||
noissuescharge => undef,
|
||||
noissueschargeguarantees => undef,
|
||||
noissueschargeguarantorswithguarantees => undef
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
my $patron = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Patrons',
|
||||
value => { categorycode => $patron_category->{categorycode} }
|
||||
}
|
||||
);
|
||||
|
||||
my $child = $builder->build_object( { class => 'Koha::Patrons' } );
|
||||
$child->add_guarantor( { guarantor_id => $patron->borrowernumber, relationship => 'parent' } );
|
||||
|
||||
# Add some charges to the child
|
||||
my $fee = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Account::Lines',
|
||||
value => {
|
||||
borrowernumber => $child->borrowernumber,
|
||||
amountoutstanding => 5.50,
|
||||
debit_type_code => 'OVERDUE',
|
||||
}
|
||||
}
|
||||
)->store;
|
||||
|
||||
# Test 1: Empty string values should be treated as "not set"
|
||||
# Empty string is the default value in sysprefs for these preferences
|
||||
t::lib::Mocks::mock_preference( 'NoIssuesChargeGuarantees', '' );
|
||||
t::lib::Mocks::mock_preference( 'NoIssuesChargeGuarantorsWithGuarantees', '' );
|
||||
|
||||
my $patron_borrowing_status = $patron->is_patron_inside_charge_limits();
|
||||
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{limit}, '',
|
||||
"Empty string limit is preserved in return value"
|
||||
);
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{charge}, 0,
|
||||
"Charges should be 0 when limit is empty string (calculation skipped)"
|
||||
);
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{overlimit}, 0,
|
||||
"Patron should not be overlimit when preference is empty string"
|
||||
);
|
||||
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{limit}, '',
|
||||
"Empty string limit is preserved for guarantors preference"
|
||||
);
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{charge}, 0,
|
||||
"Guarantors charges should be 0 when limit is empty string (calculation skipped)"
|
||||
);
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{overlimit}, 0,
|
||||
"Patron should not be overlimit when guarantors preference is empty string"
|
||||
);
|
||||
|
||||
# Test 2: Zero values should trigger calculation but not block
|
||||
t::lib::Mocks::mock_preference( 'NoIssuesChargeGuarantees', 0 );
|
||||
t::lib::Mocks::mock_preference( 'NoIssuesChargeGuarantorsWithGuarantees', 0 );
|
||||
|
||||
$patron_borrowing_status = $patron->is_patron_inside_charge_limits();
|
||||
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{limit}, 0,
|
||||
"Zero limit is preserved in return value"
|
||||
);
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{charge}, 5.50,
|
||||
"Charges should be calculated when limit is 0 (valid number)"
|
||||
);
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{overlimit}, 0,
|
||||
"Patron should not be overlimit when limit is 0 (0 means no blocking)"
|
||||
);
|
||||
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{limit}, 0,
|
||||
"Zero limit is preserved for guarantors preference"
|
||||
);
|
||||
|
||||
# Note: This will be 5.50 because the child is included in guarantor debt calculation
|
||||
ok(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{charge} > 0,
|
||||
"Guarantors charges should be calculated when limit is 0 (valid number)"
|
||||
);
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantorsWithGuarantees}->{overlimit}, 0,
|
||||
"Patron should not be overlimit when guarantors limit is 0 (0 means no blocking)"
|
||||
);
|
||||
|
||||
# Test 3: Verify that a small positive number triggers calculation and can block
|
||||
t::lib::Mocks::mock_preference( 'NoIssuesChargeGuarantees', 5.00 );
|
||||
|
||||
$patron_borrowing_status = $patron->is_patron_inside_charge_limits();
|
||||
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{charge}, 5.50,
|
||||
"Charges are calculated with positive limit"
|
||||
);
|
||||
is(
|
||||
$patron_borrowing_status->{NoIssuesChargeGuarantees}->{overlimit}, 1,
|
||||
"Patron is correctly marked overlimit when charges exceed limit"
|
||||
);
|
||||
|
||||
$schema->storage->txn_rollback;
|
||||
};
|
||||
|
||||
subtest 'Scrub the note fields' => sub {
|
||||
plan tests => 4;
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use Test::MockModule;
|
|||
use Koha::Database;
|
||||
use Koha::Patron::Relationships;
|
||||
use Koha::DateUtils qw(dt_from_string);
|
||||
use Koha::ActionLogs;
|
||||
|
||||
use File::Temp qw(tempfile tempdir);
|
||||
my $temp_dir = tempdir( 'Koha_patrons_import_test_XXXX', CLEANUP => 1, TMPDIR => 1 );
|
||||
|
|
@ -1497,7 +1498,9 @@ subtest 'patron_attributes' => sub {
|
|||
}
|
||||
|
||||
subtest 'update existing patron' => sub {
|
||||
plan tests => 19;
|
||||
plan tests => 22;
|
||||
|
||||
t::lib::Mocks::mock_preference( 'BorrowersLog', 0 );
|
||||
|
||||
my $patron = $builder->build_object(
|
||||
{
|
||||
|
|
@ -1523,8 +1526,19 @@ subtest 'patron_attributes' => sub {
|
|||
|
||||
is( $result->{overwritten}, 1 );
|
||||
|
||||
my $action_logs = Koha::ActionLogs->search(
|
||||
{
|
||||
module => "MEMBERS",
|
||||
action => "MODIFY",
|
||||
object => $patron->borrowernumber,
|
||||
}
|
||||
);
|
||||
is( $action_logs->count, 0, ' ' );
|
||||
|
||||
compare_patron_attributes( $patron->extended_attributes->unblessed, {%$attributes} );
|
||||
|
||||
t::lib::Mocks::mock_preference( 'BorrowersLog', 1 );
|
||||
|
||||
# Adding a new non-repeatable attribute
|
||||
my $new_attributes = {
|
||||
$normal_attribute_type->code => ['my normal attribute 2'],
|
||||
|
|
@ -1544,6 +1558,35 @@ subtest 'patron_attributes' => sub {
|
|||
# The normal_attribute_type has been replaced with 'my normal attribute 2'
|
||||
compare_patron_attributes( $patron->extended_attributes->unblessed, { %$attributes, %$new_attributes } );
|
||||
|
||||
$action_logs = Koha::ActionLogs->search(
|
||||
{
|
||||
module => "MEMBERS",
|
||||
action => "MODIFY",
|
||||
object => $patron->borrowernumber,
|
||||
}
|
||||
);
|
||||
|
||||
is( $action_logs->count, 1, ' ' );
|
||||
|
||||
$result = $patrons_import->import_patrons(
|
||||
{
|
||||
file => $fh,
|
||||
matchpoint => 'cardnumber',
|
||||
overwrite_cardnumber => 1,
|
||||
preserve_extended_attributes => 1
|
||||
}
|
||||
);
|
||||
|
||||
$action_logs = Koha::ActionLogs->search(
|
||||
{
|
||||
module => "MEMBERS",
|
||||
action => "MODIFY",
|
||||
object => $patron->borrowernumber,
|
||||
}
|
||||
);
|
||||
|
||||
is( $action_logs->count, 1, ' ' );
|
||||
|
||||
# UniqueIDConstraint
|
||||
$patron->extended_attributes->delete; # reset
|
||||
$builder->build_object(
|
||||
|
|
|
|||
|
|
@ -727,14 +727,16 @@ subtest "test numbering pattern with dates in GetSeq GetNextSeq" => sub {
|
|||
'GetSeq correctly calculates numbering from first aqui date, leap year'
|
||||
);
|
||||
|
||||
my $planneddate = '1970-11-01';
|
||||
($numbering) = GetNextSeq( $subscription, $pattern, undef, $planneddate );
|
||||
is( $numbering, '1970 1 Sunday 11 November', 'GetNextSeq correctly calculates numbering from planned date' );
|
||||
$planneddate = '2024-02-29';
|
||||
($numbering) = GetNextSeq( $subscription, $pattern, undef, $planneddate );
|
||||
my $planneddate = '1970-10-01';
|
||||
my $nextpublisheddate = '1970-11-01';
|
||||
($numbering) = GetNextSeq( $subscription, $pattern, undef, $planneddate, $nextpublisheddate );
|
||||
is( $numbering, '1970 1 Sunday 11 November', 'GetNextSeq correctly calculates numbering from next published date' );
|
||||
$planneddate = '2024-01-29';
|
||||
$nextpublisheddate = '2024-02-29';
|
||||
($numbering) = GetNextSeq( $subscription, $pattern, undef, $planneddate, $nextpublisheddate );
|
||||
is(
|
||||
$numbering, '2024 29 Thursday 2 February',
|
||||
'GetNextSeq correctly calculates numbering from planned date, leap year'
|
||||
'GetNextSeq correctly calculates numbering from next published date, leap year'
|
||||
);
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -274,6 +274,8 @@ sub _next_seq {
|
|||
$seq, $subscription->{lastvalue1}, $subscription->{lastvalue2},
|
||||
$subscription->{lastvalue3}, $subscription->{innerloop1},
|
||||
$subscription->{innerloop2}, $subscription->{innerloop3}
|
||||
) = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate );
|
||||
)
|
||||
= GetNextSeq( $subscription, $pattern, $frequency, $publisheddate, $publisheddate )
|
||||
; # 5th parameter not really used btw
|
||||
return $seq;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ subtest 'Reserves with itemtype' => sub {
|
|||
patron_id => int( $patron_1->borrowernumber ),
|
||||
biblio_id => int( $biblio_1->biblionumber ),
|
||||
pickup_library_id => $branchcode,
|
||||
item_type => $itemtype,
|
||||
item_type_id => $itemtype,
|
||||
};
|
||||
|
||||
$t->delete_ok("//$userid_3:$password@/api/v1/holds/$reserve_id")->status_is( 204, 'REST3.2.4' )
|
||||
|
|
@ -294,7 +294,7 @@ subtest 'Reserves with itemtype' => sub {
|
|||
$reserve_id = $t->tx->res->json->{hold_id};
|
||||
|
||||
$t->get_ok( "//$userid_1:$password@/api/v1/holds?patron_id=" . $patron_1->borrowernumber )->status_is(200)
|
||||
->json_is( '/0/hold_id', $reserve_id )->json_is( '/0/item_type', $itemtype );
|
||||
->json_is( '/0/hold_id', $reserve_id )->json_is( '/0/item_type_id', $itemtype );
|
||||
};
|
||||
|
||||
subtest 'test AllowHoldDateInFuture' => sub {
|
||||
|
|
@ -769,7 +769,7 @@ subtest 'add() tests (maxreserves behaviour)' => sub {
|
|||
patron_id => $patron->borrowernumber,
|
||||
biblio_id => $biblio_1->biblionumber,
|
||||
pickup_library_id => $item_1->home_branch->branchcode,
|
||||
item_type => $item_1->itype,
|
||||
item_type_id => $item_1->itype,
|
||||
};
|
||||
|
||||
$t->post_ok( "//$userid:$password@/api/v1/holds" => json => $post_data )->status_is(201);
|
||||
|
|
|
|||
|
|
@ -586,7 +586,7 @@ subtest 'add() tests' => sub {
|
|||
|
||||
subtest 'extended_attributes handling tests' => sub {
|
||||
|
||||
plan tests => 25;
|
||||
plan tests => 29;
|
||||
|
||||
$mocked_patron->mock(
|
||||
'extended_attributes',
|
||||
|
|
@ -622,6 +622,23 @@ subtest 'add() tests' => sub {
|
|||
|
||||
is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
|
||||
|
||||
# Bug 40219: Test that welcome email is not sent when extended attribute validation fails
|
||||
t::lib::Mocks::mock_preference( 'AutoEmailNewUser', 1 );
|
||||
$letter_enqueued = 0;
|
||||
$extended_attrs_exception = 'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute';
|
||||
$t->post_ok(
|
||||
"//$userid:$password\@/api/v1/patrons" => json => {
|
||||
"firstname" => "Bug",
|
||||
"surname" => "FortyZeroTwoOneNine",
|
||||
"address" => "Somewhere",
|
||||
"category_id" => "ST",
|
||||
"city" => "TestCity",
|
||||
"library_id" => "MPL",
|
||||
"email" => 'bug40219@test.com'
|
||||
}
|
||||
)->status_is(400)->json_is( '/error' => "Missing mandatory extended attribute (type=$type)" );
|
||||
is( $letter_enqueued, 0, 'Bug 40219: No welcome email sent when extended attribute validation fails' );
|
||||
|
||||
$extended_attrs_exception = 'Koha::Exceptions::Patron::Attribute::InvalidType';
|
||||
$t->post_ok(
|
||||
"//$userid:$password@/api/v1/patrons" => json => {
|
||||
|
|
|
|||
13
yarn.lock
13
yarn.lock
|
|
@ -1823,7 +1823,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
|
||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||
|
||||
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0":
|
||||
"@types/express-serve-static-core@*":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz#74f47555b3d804b54cb7030e6f9aa0c7485cfc5b"
|
||||
integrity sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==
|
||||
|
|
@ -1843,16 +1843,7 @@
|
|||
"@types/range-parser" "*"
|
||||
"@types/send" "*"
|
||||
|
||||
"@types/express@*":
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.5.tgz#3ba069177caa34ab96585ca23b3984d752300cdc"
|
||||
integrity sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "^5.0.0"
|
||||
"@types/serve-static" "^1"
|
||||
|
||||
"@types/express@^4.17.13", "@types/express@^4.17.21":
|
||||
"@types/express@*", "@types/express@^4.17.13", "@types/express@^4.17.21":
|
||||
version "4.17.25"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.25.tgz#070c8c73a6fee6936d65c195dbbfb7da5026649b"
|
||||
integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==
|
||||
|
|
|
|||
Loading…
Reference in New Issue