Merge remote-tracking branch 'upstream/25.05.x' into HEAD

This commit is contained in:
Wainui Witika-Park 2026-08-05 16:20:01 +12:00
commit 0dff1745ae
No known key found for this signature in database
GPG Key ID: 2D0A6AB5DAF1B78D
40 changed files with 256 additions and 121 deletions

View File

@ -583,17 +583,19 @@ sub _new_Zconn {
$syntax = 'xml';
$elementSetName = 'marcxml';
my $host = _common_config( $server, 'listen' )->{content};
my $host = _common_config( $server, 'listen' );
$host = $host->{content};
my $serverinfo = _common_config( $server, 'serverinfo' );
my $user = $serverinfo->{user};
my $password = $serverinfo->{password};
eval {
# set options
my $o = ZOOM::Options->new();
$o->option( user => $user ) if $user && $password;
$o->option( password => $password ) if $user && $password;
$o->option( async => 1 ) if $async;
$o->option( cqlfile => _common_config( $server, 'server' )->{cql2rpn} );
$o->option( user => $user ) if $user && $password;
$o->option( password => $password ) if $user && $password;
$o->option( async => 1 ) if $async;
my $server_conf = _common_config( $server, 'server' );
$o->option( cqlfile => $server_conf->{cql2rpn} );
$o->option( cclfile => $serverinfo->{ccl2rpn} );
$o->option( preferredRecordSyntax => $syntax );
$o->option( elementSetName => $elementSetName ) if $elementSetName;

View File

@ -931,6 +931,7 @@ sub marc2cites {
if ( $marcflavour eq "UNIMARC" ) {
%publication = (
title => $record->subfield( "200", "a" ) || "",
subtitle => $record->subfield( "200", "e" ) || "",
place => $record->subfield( "210", "a" ) || "",
publisher => $record->subfield( "210", "c" ) || "",
date => $record->subfield( "210", "d" ) || $record->subfield( "210", "h" ) || ""
@ -938,6 +939,7 @@ sub marc2cites {
} else {
%publication = (
title => $record->subfield( "245", "a" ) || "",
subtitle => $record->subfield( "245", "b" ) || "",
place => $record->subfield( "264", "a" ) || $record->subfield( "260", "a" ) || "",
publisher => $record->subfield( "264", "b" ) || $record->subfield( "260", "b" ) || "",
date => $record->subfield( "264", "c" )
@ -978,13 +980,16 @@ sub marc2cites {
if ( $publication{date} ) {
$cites{'Harvard'} .= ' (' . $publication{'date'} . '). ';
$cites{'Chicago'} .= ' ' . $publication{'date'} . '. ';
$cites{'MLA'} .= ' ' . $publication{'title'} . '. ';
$cites{'APA'} .= ' (' . $publication{'date'} . '). ';
$cites{'MLA'} .=
' ' . $publication{'title'} . ( $publication{'subtitle'} ? ': ' . $publication{'subtitle'} : '' ) . '. ';
$cites{'APA'} .= ' (' . $publication{'date'} . '). ';
}
$cites{'Harvard'} .= $publication{'title'} . '. ';
$cites{'Chicago'} .= $publication{'title'} . '. ';
$cites{'MLA'} .= $publication{'place'} . ': ';
$cites{'APA'} .= $publication{'title'} . '. ';
$cites{'Harvard'} .=
$publication{'title'} . ( $publication{'subtitle'} ? ': ' . $publication{'subtitle'} : '' ) . '. ';
$cites{'Chicago'} .=
$publication{'title'} . ( $publication{'subtitle'} ? ': ' . $publication{'subtitle'} : '' ) . '. ';
$cites{'MLA'} .= $publication{'place'} . ': ';
$cites{'APA'} .= $publication{'title'} . ( $publication{'subtitle'} ? ': ' . $publication{'subtitle'} : '' ) . '. ';
$cites{'Harvard'} .= $publication{'place'} . ': ';
$cites{'Chicago'} .= $publication{'place'} . ': ';
$cites{'MLA'} .= $publication{'publisher'} . '. ';

View File

@ -342,9 +342,10 @@ sub days_between {
my $end_dt = shift;
# Change time zone for date math and swap if needed
$start_dt = $start_dt->clone->set_time_zone('floating');
$end_dt = $end_dt->clone->set_time_zone('floating');
$start_dt = $start_dt->clone->set_time_zone('floating')->truncate( to => 'day' );
$end_dt = $end_dt->clone->set_time_zone('floating')->truncate( to => 'day' );
if ( $start_dt->compare($end_dt) > 0 ) {
( $start_dt, $end_dt ) = ( $end_dt, $start_dt );
}
@ -448,6 +449,20 @@ Koha::Calendar - Object containing a branches calendar
=head1 METHODS
=head2 _init
$calendar->_init();
Populates the object with repeatable closed days (weekly and day/month) for the
branch. Called automatically by C<new>.
=head2 _holidays
my $holidays = $calendar->_holidays();
Returns a hashref of special (one-off) holidays for the branch, keyed by
C<YYYYMMDD> date strings. Results are cached.
=head2 new : Create a calendar object
my $calendar = Koha::Calendar->new( branchcode => 'MAIN' );

View File

@ -785,7 +785,7 @@ sub cancel {
type => 'RESERVE_EXPIRED',
item_id => $self->itemnumber
}
) if $charge;
) if $charge && $charge > 0;
}
C4::Log::logaction( 'HOLDS', 'CANCEL', $self->reserve_id, $self, undef, $original )

View File

@ -2067,7 +2067,9 @@ Returns the effective bookability of the current item, be that item or itemtype
sub effective_bookable {
my ($self) = @_;
return $self->bookable // $self->itemtype->bookable;
return $self->bookable if defined $self->bookable;
return $self->itemtype->bookable if $self->itemtype;
return 0;
}
=head3 orders

View File

@ -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++;

View File

@ -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;
}

View File

@ -100,7 +100,7 @@ sub store {
# can be undef for branch=any suggestions
my $library = $result->library;
if ( $library && $library->inbound_email_address ) {
$toaddress = $library->inbound_email_address;
$toaddress = $library->inbound_email_address;
}
} elsif ( $emailpurchasesuggestions eq "KohaAdminEmailAddress" ) {
$toaddress = C4::Context->preference('ReplytoDefault')

View File

@ -37,7 +37,7 @@ my $builder = sub {
function Click$function_name(event) {
defaultvalue=document.getElementById(event.data.id).value;
newin=window.open(\"../cataloguing/plugin_launcher.pl?plugin_name=marc21_field_006.pl&index=\"+ event.data.id +\"&result=\"+defaultvalue,\"tag_editor\",'width=1000,height=600,toolbar=false,scrollbars=yes');
newin=window.open(\"../cataloguing/plugin_launcher.pl?plugin_name=marc21_field_006.pl&index=\"+ event.data.id +\"&result=\"+encodeURIComponent(defaultvalue),\"tag_editor\",'width=1000,height=600,toolbar=false,scrollbars=yes');
}
</script>

View File

@ -98,13 +98,7 @@ my $include_aqorders_qty_join =
my $nfl_comparison = $include_ordered ? '<=' : '=';
my $sus_comparison = $include_suspended ? '<=' : '<';
my $strsth = "SELECT reservedate,
reserves.borrowernumber as borrowernumber,
reserves.biblionumber,
reserves.branchcode as branch,
items.holdingbranch,
items.itemcallnumber,
items.itemnumber,
my $strsth = "SELECT reserves.biblionumber,
GROUP_CONCAT(DISTINCT items.itemcallnumber
ORDER BY items.itemnumber SEPARATOR '|') as listcall,
GROUP_CONCAT(DISTINCT homebranch
@ -117,8 +111,6 @@ my $strsth = "SELECT reservedate,
ORDER BY items.itemnumber SEPARATOR '|') as l_itype,
GROUP_CONCAT(DISTINCT items.ccode
ORDER BY items.ccode SEPARATOR '|') as l_ccode,
reserves.found,
biblio.title,
biblio.subtitle,
biblio.medium,
@ -142,7 +134,10 @@ if ( C4::Context->preference('IndependentBranches') ) {
push @query_params, C4::Context->userenv->{'branch'};
}
$strsth .= " GROUP BY reserves.biblionumber ORDER BY reservecount DESC";
$strsth .= " GROUP BY reserves.biblionumber, biblio.title, biblio.subtitle,
biblio.medium, biblio.part_number, biblio.part_name, biblio.author
ORDER BY reservecount DESC
";
$template->param( sql => $strsth );
my $sth = $dbh->prepare($strsth);
@ -156,22 +151,15 @@ while ( my $data = $sth->fetchrow_hashref ) {
push(
@reservedata,
{
reservedate => $data->{reservedate},
priority => $data->{priority},
name => $data->{borrower},
title => $data->{title},
subtitle => $data->{subtitle},
medium => $data->{medium},
part_number => $data->{part_number},
part_name => $data->{part_name},
author => $data->{author},
itemnum => $data->{itemnumber},
biblionumber => $data->{biblionumber},
holdingbranch => $data->{holdingbranch},
homebranch_list => [ split( '\|', $data->{homebranch_list} ) ],
holdingbranch_list => [ split( '\|', $data->{holdingbranch_list} ) ],
branch => $data->{branch},
itemcallnumber => $data->{itemcallnumber},
location => [ split( '\|', $data->{l_location} ) ],
itype => [ split( '\|', $data->{l_itype} ) ],
ccode => [ split( '\|', $data->{l_ccode} ) ],

View File

@ -72,7 +72,7 @@ requires 'Locale::Messages', '1.20';
requires 'Locale::PO', '0.24';
requires 'Log::Log4perl', '1.29';
requires 'MARC::Charset', '0.98';
requires 'MARC::File::XML', 'v1.0.1';
requires 'MARC::File::XML', 'v1.0.5';
requires 'MARC::Lint', 'v1.52';
requires 'MARC::Record', 'v2.0.6';
requires 'MARC::Record::MiJ', '0.04';

View File

@ -896,6 +896,8 @@ fi
if [ "$op" = create ] || [ "$op" = populate ] || [ "$op" = use ]
then
username="$name-koha"
# Reconfigure Apache.
if ! {
a2ensite "$name" > /dev/null 2>&1 ||

View File

@ -78,7 +78,7 @@
<img src="[% image_location | html %]" alt="" />
[% ELSE %]
<input type="radio" id="remote_image_check" name="image" value="remoteImage" />
<input type="text" name="remoteImage" size="48" maxlength="200" value="" onmousedown="document.getElementById('remote_image_check').checked = true;" />
<input type="text" id="remoteImage" name="remoteImage" size="48" maxlength="200" value="" />
[% END %]
[% END %]
[% END # /tab_panels %]

View File

@ -271,7 +271,7 @@
<li><a class="dropdown-item" href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber | uri %]">Place hold</a></li>
<li
><a class="dropdown-item" href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber | uri %]&amp;findborrower=[% holdfor_patron.cardnumber | uri %]"
>Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 %]</a
>Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_html => 1 %]</a
></li
>
</ul>

View File

@ -47,7 +47,7 @@
[%- SET display_patron_name = 0 -%]
[%- END -%]
[%- END -%]
[%- IF hide_patron_infos_if_needed AND ( display_patron_name OR display_cardnumber ) -%]
[%- IF !no_html AND (display_patron_name OR display_cardnumber) -%]
[%- IF link_to == 'circulation_reserves' -%]
<a href="/cgi-bin/koha/circ/circulation.pl?borrowernumber=[%- data.borrowernumber | uri -%]#reserves">[%- PROCESS display_patron_name -%]</a>
[%- ELSIF link_to == 'circulation_recalls' -%]

View File

@ -444,6 +444,10 @@
$("#library_limitation")[0].style.minWidth = "450px";
$("#library_limitation").select2();
}
$("#remoteImage").on("mousedown", function () {
$("#remote_image_check").prop("checked", true);
});
});
</script>
[% END %]

View File

@ -572,6 +572,10 @@
$("#library_limitation")[0].style.minWidth = "450px";
$("#library_limitation").select2();
}
$("#remoteImage").on("mousedown", function () {
$("#remote_image_check").prop("checked", true);
});
});
</script>
[% END %]

View File

@ -327,7 +327,7 @@
<fieldset class="rows" id="match_checks">
<h4>Required match checks</h4>
[% IF ( edit_matching_rule ) %]
[% SET p_style = matchpoints ? 'display:none;' : 'display:block;' %]
[% SET p_style = matchchecks ? 'display:none;' : 'display:block;' %]
<p id="addMatchCheck" style="[% p_style | html %]">
<a href="#" class="btn btn-link" onclick="InsertMatchcheck('mc_1', 'mc_template'); return false;"><i class="fa fa-plus" aria-hidden="true"></i> New match check</a></p
>

View File

@ -105,7 +105,7 @@ Cataloging:
choices:
1: specific item
0: bibliographic record
- as the authoritative item type (for determining circulation and fine rules, for displaying an item type icon on either opac detail or results page, etc).
- as the authoritative item type (for determining circulation and fine rules, for displaying an item type icon on either OPAC detail or results page, etc).
-
- Map the MARC subfields
- pref: itemcallnumber
@ -284,7 +284,7 @@ Cataloging:
- pref: OpacSuppressionByIPRange
class: short
- (Leave blank if not used. Define a range like <code>192.168.</code>.)
- <br />Redirect the opac detail page for suppressed records to
- <br />Redirect the OPAC detail page for suppressed records to
- pref: OpacSuppressionRedirect
choices:
1: "an explanatory page ('This record is blocked')."

View File

@ -103,7 +103,7 @@ Enhanced content:
-
- Access Novelist Select using user profile
- pref: NovelistSelectProfile
- on the opac
- on the OPAC
-
- Access Novelist Select using user profile
- pref: NovelistSelectStaffProfile

View File

@ -72,7 +72,7 @@
<legend>Merge reference</legend>
<ol>
<li class="radio"
><input type="radio" value="[% recordid1 | uri %]" checked="checked" id="mergereference1" name="mergereference" onclick="changeFramework('[% frameworkcode1 | html %]')" /><label for="mergereference1"
><input type="radio" value="[% recordid1 | uri %]" checked="checked" id="mergereference1" name="mergereference" class="change_framework" data-framework="[% frameworkcode1 | html %]" /><label for="mergereference1"
>[% title1 | html %] [% FOREACH subtitl1 IN subtitle1 %][% subtitl1 | html %][% END %] (<a
href="/cgi-bin/koha/authorities/detail.pl?authid=[% recordid1 | uri %]"
title="MARC"
@ -83,7 +83,7 @@
></li
>
<li class="radio"
><input type="radio" value="[% recordid2 | uri %]" id="mergereference2" name="mergereference" onclick="changeFramework('[% frameworkcode2 | html %]')" /><label for="mergereference2"
><input type="radio" value="[% recordid2 | uri %]" id="mergereference2" name="mergereference" class="change_framework" data-framework="[% frameworkcode2 | html %]" /><label for="mergereference2"
>[% title2 | html %] [% FOREACH subtitl2 IN subtitle2 %][% subtitl2 | html %][% END %] (<a
href="/cgi-bin/koha/authorities/detail.pl?authid=[% recordid2 | uri %]"
title="MARC"
@ -166,11 +166,15 @@
if( $("#tabs .tab-pane.active").length < 1 ){
$("#tabs a:first").tab("show");
}
$(".change_framework").on("click", function () {
let framework = $(this).data("framework");
changeFramework(framework);
});
});
function changeFramework(fw) {
$("#Frameworks").val(fw);
$("#frameworkcode").val(fw);
}
</script>
[% END %]

View File

@ -165,9 +165,9 @@
<ul class="dropdown-menu">
<li><a href="#" class="placehold dropdown-item">Place hold</a></li>
[% IF holdfor %]
<li><a href="#" class="placeholdfor dropdown-item">Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_cardnumber => 1 %]</a></li>
<li><a href="#" class="placeholdfor dropdown-item">Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_cardnumber => 1 no_html => 1 %]</a></li>
<li><hr class="dropdown-divider" /></li>
<li><a class="dropdown-item" href="#" id="forgetholdfor">Forget [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_cardnumber => 1 %]</a></li>
<li><a class="dropdown-item" href="#" id="forgetholdfor">Forget [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_cardnumber => 1 no_html => 1 %]</a></li>
[% END %]
[% IF holdforclub %]
<li><a href="#" class="placeholdforclub dropdown-item">Place hold for [% holdforclub_name | html %]</a></li>
@ -693,7 +693,7 @@
<span class="holdforlink"
>|
<a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% SEARCH_RESULT.biblionumber | uri %]&amp;findborrower=[% holdfor_patron.cardnumber | uri %]"
>Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_cardnumber => 1 %]</a
>Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_cardnumber => 1 no_html => 1 %]</a
></span
>
[% END %]

View File

@ -132,10 +132,19 @@
checked="checked"
id="ref_biblionumber[% record.biblionumber | html %]"
name="ref_biblionumber"
onclick="changeFramework('[% record.frameworkcode | html %]')"
class="change_framework"
data-framework="[% record.frameworkcode | html %]"
/>
[% SET selected_frameworkcode = record.frameworkcode %]
[% ELSE %]
<input type="radio" value="[% record.biblionumber | html %]" id="ref_biblionumber[% record.biblionumber | html %]" name="ref_biblionumber" onclick="changeFramework('[% record.frameworkcode | html %]')" />
<input
type="radio"
value="[% record.biblionumber | html %]"
id="ref_biblionumber[% record.biblionumber | html %]"
name="ref_biblionumber"
class="change_framework"
data-framework="[% record.frameworkcode | html %]"
/>
[% END %]
<label for="ref_biblionumber[% record.biblionumber | html %]">
[% INCLUDE 'biblio-title.inc' biblio=record.data link = 1 %] ([% record.biblionumber | uri %])
@ -149,7 +158,7 @@
<select name="frameworkcode" id="frameworkcode">
<option value="">Default</option>
[% FOREACH framework IN frameworks %]
<option value="[% framework.frameworkcode | html %]">[% framework.frameworktext | html %]</option>
<option value="[% framework.frameworkcode | html %]" [% IF framework.frameworkcode == selected_frameworkcode %]selected="selected"[% END %]>[% framework.frameworktext | html %]</option>
[% END %]
</select>
</li>
@ -235,6 +244,10 @@
$("#dataPreviewLabel").html("");
$("#dataPreview .modal-body").html("<div id=\"loading\"><img src=\"[% interface | html %]/[% theme | html %]/img/spinner-small.gif\" alt=\"\" /> "+_("Loading")+"</div>");
});
$(".change_framework").on("click", function(){
let framework = $(this).data("framework");
changeFramework( framework );
});
});
[% ELSE %]
function check_mandatory () {

View File

@ -434,7 +434,7 @@
</li>
<li class="cost">
<label class="cost">Cost:</label>
[% IF request.cost %][% request.cost | $Price %][% ELSE %]<span>N/A</span>[% END %]
[% IF request.cost.defined %][% request.cost | $Price %][% ELSE %]<span>N/A</span>[% END %]
</li>
<li class="price_paid">
<label class="price_paid">Price paid:</label>
@ -700,13 +700,13 @@
[% type_disclaimer_date | $KohaDates with_hours => 1 %]
</li>
[% END %]
[% IF request.cost %]
[% IF request.cost.defined %]
<li class="cost">
<span class="label cost">Cost:</span>
[% request.cost | $Price %]
</li>
[% END %]
[% IF request.price_paid %]
[% IF request.price_paid.defined %]
<li class="price_paid">
<span class="label price_paid">Price paid:</span>
[% request.price_paid | $Price %]

View File

@ -18,7 +18,7 @@
[% INCLUDE 'doc-head-close.inc' %]
</head>
<body id="labels_label-edit-batch" class="tools labels" onload="dofocus();">
<body id="labels_label-edit-batch" class="tools labels">
[% WRAPPER 'header.inc' %]
[% INCLUDE 'cat-search.inc' %]
[% END %]
@ -340,6 +340,7 @@
}
$(document).ready(function() {
dofocus();
$('.sidebar_menu a[href$="/cgi-bin/koha/labels/label-home.pl"]').addClass("current");
[% IF table_loop %]
$("#description").show();

View File

@ -479,7 +479,7 @@
[% IF patron_category.category_type == 'I' %]
<li class="guarantor-details" data-borrowernumber="[% r.guarantor_id | $raw %]">
<span class="label">Organization:</span>
[% INCLUDE 'patron-title.inc' patron=r.guarantor hide_patron_infos_if_needed=1 %]
[% INCLUDE 'patron-title.inc' patron=r.guarantor %]
</li>
<li>
<span class="label">Relationship:</span>
@ -488,7 +488,7 @@
[% ELSE %]
<li class="guarantor-details" data-borrowernumber="[% r.guarantor_id | $raw %]">
<span class="label">Guarantor:</span>
[% INCLUDE 'patron-title.inc' patron=r.guarantor hide_patron_infos_if_needed=1 %]
[% INCLUDE 'patron-title.inc' patron=r.guarantor %]
<input type="hidden" class="guarantor_id relation-[% r.id | html %]" name="guarantor_id" value="[% r.guarantor_id | html %]" />
</li>
<li>

View File

@ -497,8 +497,8 @@
[% END %]
let table_dt = table.DataTable();
$("#hide_received_orders").click(function(e){
let table_dt = table?.DataTable();
$("#hide_received_orders").click(function (e) {
e.preventDefault();
table_dt.columns(4).search('^$', {regex: true}).draw(); // Not empty "Receive date" columns
});

View File

@ -1349,7 +1349,7 @@
}
},
{
data: "me.title",
data: "me.title:me.author:me.copyrightdate:me.volumedesc:me.isbn:me.publishercode:me.publicationyear:me.place:me.collectiontitle:me.itemtype:me.note",
searchable: true,
orderable: true,
render: function (data, type, row, meta) {

View File

@ -366,7 +366,7 @@
<span class="holdforlink"
>|
<a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% itemsloo.biblionumber | uri %]&amp;findborrower=[% holdfor_patron.cardnumber | uri %]"
>Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 %]</a
>Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_html => 1 %]</a
></span
>
[% END %]

View File

@ -68,6 +68,10 @@ KOHA.Preferences = {
$(form)
.find(".modified")
.each(function () {
// set defaultValue to changed value - enables re-saving when input changes back to initial value
if (this.defaultValue != undefined) {
this.defaultValue = this.value;
}
var modified_pref = $(this).attr("id");
modified_pref = modified_pref.replace("pref_", "");
msg +=

View File

@ -720,7 +720,7 @@
<th>Contents</th>
<th>Type</th>
<th>Modification date</th>
<th>&nbsp;</th>
<th>Actions</th>
</tr>
</thead>
<tbody>

View File

@ -238,12 +238,7 @@ if ( $op eq 'cud-edit_note' ) {
$schema->txn_do(
sub {
# Update the note and date in the account line
$payment->set(
{
date => \'NOW()',
note => $note
}
)->store();
$payment->set( { note => $note } )->store();
}
);
}

View File

@ -221,8 +221,8 @@ GetOptions(
'pseudo-transactions:i' => \$pPseudoTransactions,
'pseudo-transactions-from:s' => \$pPseudoTransactionsFrom,
'pseudo-transactions-to:s' => \$pPseudoTransactionsTo,
'labels' => \$labels,
'cards' => \$cards,
'labels:i' => \$labels,
'cards:i' => \$cards,
'return-claims' => \$return_claims,
'jobs-type:s' => \@jobs_types,
'jobs-days:i' => \$jobs_days,
@ -524,7 +524,7 @@ if ($verbose) {
say $confirm ? sprintf( "Deleted %d patrons", $count ) : sprintf( "%d patrons would have been deleted", $count );
}
if ($pExpSelfReg) {
if ( $pExpSelfReg && C4::Context->preference('PatronSelfRegistration') ) {
try {
my $opac_registrations = Koha::Patrons->search->filter_by_expired_opac_registrations->filter_by_safe_to_delete;
my $count = $opac_registrations->count;
@ -771,11 +771,11 @@ if ($labels) {
if ($cards) {
print "Purging card creator batches last added to more than $cards days ago.\n" if $verbose;
my $count = PurgeCreatorBatches( $labels, 'patroncards', $confirm );
my $count = PurgeCreatorBatches( $cards, 'patroncards', $confirm );
if ($verbose) {
say $confirm
? sprintf "Done with purging %d card creator batches last added to more than %d days ago.\n", $count,
$labels
$cards
: sprintf "%d card creator batches would have been purged.", $count;
}
}

View File

@ -294,53 +294,43 @@ for ( my $tabloop = 0 ; $tabloop <= 9 ; $tabloop++ ) {
$template->param( "tab" . $tabloop . "XX" => \@loop_data );
}
# now, build item tab !
# the main difference is that datas are in lines and not in columns : thus, we build the <th> first, then the values...
# loop through each tag
# warning : we may have different number of columns in each row. Thus, we first build a hash, complete it if necessary
# then construct template.
# $record has already had all the item fields filtered above.
my @fields = $record->fields();
my %witness; #---- stores the list of subfields used at least once, with the "meaning" of the code
# Now, build item tab!
# Data is in rows (one per item) not columns: build headers (%witness) and row data (@item_loop).
$items->reset();
my @fields = map { $_->as_marc_field } $items->as_list;
my $item_tag = $fields[0] ? $fields[0]->tag() : undef;
my $item_tagslib = $item_tag ? $tagslib->{$item_tag} : {};
my %witness;
my @item_subfield_codes;
my @item_loop;
my %date_fields = map { $_ => 1 }
qw( items.dateaccessioned items.onloan items.datelastseen items.datelastborrowed items.replacementpricedate );
foreach my $field (@fields) {
next if ( $field->tag() < 10 );
my @subf = $field->subfields;
my $item;
# loop through each subfield
for my $i ( 0 .. $#subf ) {
my $sf_def = $tagslib->{ $field->tag() }->{ $subf[$i][0] };
next if ( ( $sf_def->{tab} || 0 ) != 10 );
for my $subf ( $field->subfields ) {
my ( $code, $value ) = @$subf;
my $sf_def = $item_tagslib->{$code};
next if ( ( $sf_def->{hidden} || 0 ) > 0 );
push @item_subfield_codes, $subf[$i][0];
$witness{ $subf[$i][0] } = $sf_def->{lib};
push @item_subfield_codes, $code;
$witness{$code} = $sf_def->{lib};
# Allow repeatables (BZ 13574)
if ( $item->{ $subf[$i][0] } ) {
$item->{ $subf[$i][0] } .= ' | ';
} else {
$item->{ $subf[$i][0] } = q{};
}
$item->{$code} = ( $item->{$code} ) ? $item->{$code} . ' | ' : q{};
if ( $sf_def->{isurl} ) {
$item->{ $subf[$i][0] } .= "<a href=\"$subf[$i][1]\">$subf[$i][1]</a>";
$item->{$code} .= "<a href=\"$value\">$value</a>";
} elsif ( $sf_def->{kohafield} eq "biblioitems.isbn" ) {
$item->{ $subf[$i][0] } .= $subf[$i][1];
$item->{$code} .= $value;
} else {
$item->{ $subf[$i][0] } .= GetAuthorisedValueDesc(
$field->tag(), $subf[$i][0],
$subf[$i][1], '', $tagslib, '', 'opac'
) // q{};
$item->{$code} .= GetAuthorisedValueDesc( $item_tag, $code, $value, '', $tagslib, '', 'opac' ) // q{};
}
my $kohafield = $tagslib->{ $field->tag() }->{ $subf[$i][0] }->{kohafield};
$item->{ $subf[$i][0] } = output_pref( { str => $item->{ $subf[$i][0] }, dateonly => 1 } )
if grep { $kohafield eq $_ }
qw( items.dateaccessioned items.onloan items.datelastseen items.datelastborrowed items.replacementpricedate );
$item->{$code} = output_pref( { str => $item->{$code}, dateonly => 1 } )
if $date_fields{ $sf_def->{kohafield} // q{} };
}
push @item_loop, $item if $item;
}

View File

@ -513,7 +513,7 @@ subtest 'hours_between | days_between' => sub {
};
subtest 'With holiday' => sub {
plan tests => 2;
plan tests => 3;
my $library = $builder->build_object( { class => 'Koha::Libraries' } );
@ -612,7 +612,7 @@ subtest 'hours_between | days_between' => sub {
$diff_hours = $calendar->hours_between( $now, $nov_13->clone->subtract( hours => 3 ) )->hours;
is( $diff_hours, 8 * 24 - 2 * 24 - 3, 'hours: 289 hours, 2 holidays ' );
$diff_days = $calendar->days_between( $now, $nov_13->clone->subtract( hours => 3 ) )->delta_days;
is( $diff_days, 8 - 1, 'days: 289 hours, 2 holidays' );
is( $diff_days, 8 - 2, 'days: 289 hours, 2 holidays' );
# Between 5th and 15th
$diff_hours = $calendar->hours_between( $now, $nov_15->clone->subtract( hours => 3 ) )->hours;
@ -621,6 +621,35 @@ subtest 'hours_between | days_between' => sub {
is( $diff_days, 10 - 2, 'days: 237 hours, 2 holidays' );
};
subtest 'Bug 42545: holiday on end_dt correctly subtracted when return time is before due time' => sub {
plan tests => 4;
# Scenario: daily loan (due time 23:59), patron returns/renews before 23:59 on a holiday.
# Before the fix, days_between did not truncate datetimes, so the loop exited before
# reaching the end date when due_time > return_time on the same calendar day.
# Nov 13 is a Wednesday (holiday). Due at end-of-day, returned early morning.
my $due_endofday = dt_from_string('2019-11-05 23:59:00');
my $return_on_holiday_early = dt_from_string('2019-11-13 09:34:56');
my $diff_days = $calendar->days_between( $due_endofday, $return_on_holiday_early )->delta_days;
is( $diff_days, 8 - 2, 'holiday on end_dt subtracted when return time is before due time (23:59)' );
# Same range but returned late (after 23:59 would be next day, so use 23:58) - no regression
my $return_on_holiday_late = dt_from_string('2019-11-13 23:58:00');
$diff_days = $calendar->days_between( $due_endofday, $return_on_holiday_late )->delta_days;
is( $diff_days, 8 - 2, 'holiday on end_dt subtracted when return time is just before due time' );
# Open end date is unaffected either way
my $return_open_early = dt_from_string('2019-11-14 09:34:56');
$diff_days = $calendar->days_between( $due_endofday, $return_open_early )->delta_days;
is( $diff_days, 9 - 2, 'open end_dt unaffected, 2 holidays still subtracted (early return)' );
my $return_open_late = dt_from_string('2019-11-14 23:58:00');
$diff_days = $calendar->days_between( $due_endofday, $return_open_late )->delta_days;
is( $diff_days, 9 - 2, 'open end_dt unaffected, 2 holidays still subtracted (late return)' );
};
};
Time::Fake->reset;

View File

@ -27,7 +27,7 @@ use Test::MockModule;
use t::lib::Mocks;
use t::lib::TestBuilder;
use t::lib::Mocks;
use t::lib::Dates;
use C4::Reserves qw(AddReserve);
@ -1332,8 +1332,12 @@ subtest 'revert_found() tests' => sub {
my $log =
Koha::ActionLogs->search( { module => 'HOLDS', action => 'MODIFY', object => $hold->reserve_id } )->next;
my $expected = sprintf q{'timestamp' => '%s'}, $hold->timestamp;
like( $log->info, qr{$expected}, 'Timestamp logged is the current one' );
if ( $log && $log->info =~ m{'timestamp' => '([^']*)'} ) {
my $timestamp = $1;
is( t::lib::Dates::compare( $timestamp, $hold->timestamp ), 0, 'Timestamp logged is the current one' );
} else {
fail('Log entry for hold modification not found or malformed');
}
my $log_count =
Koha::ActionLogs->search( { module => 'HOLDS', action => 'MODIFY', object => $hold->reserve_id } )->count;

View File

@ -3846,7 +3846,7 @@ subtest 'effective_not_for_loan_status() tests' => sub {
subtest 'effective_bookable() tests' => sub {
plan tests => 5;
plan tests => 7;
$schema->storage->txn_begin;
@ -3897,5 +3897,22 @@ subtest 'effective_bookable() tests' => sub {
'->effective_bookable returns item specific bookable value when item bookable is defined'
);
my $biblio_no_itype = $builder->build_sample_biblio;
$biblio_no_itype->biblioitem->set( { itemtype => undef } )->store;
my $item_no_itype = $builder->build_sample_item(
{
biblionumber => $biblio_no_itype->biblionumber,
itype => undef,
}
);
warning_like {
is(
$item_no_itype->effective_bookable, 0,
'->effective_bookable returns 0 when item has no itemtype and item bookable is undefined'
);
}
qr/item-level_itypes set but no itemtype set for item/,
'Warning raised for missing itemtype when item-level_itypes is set';
$schema->storage->txn_rollback;
};

View File

@ -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(

View File

@ -3,7 +3,7 @@
use Modern::Perl;
use Test::NoWarnings;
use Test::More tests => 16;
use Test::More tests => 17;
use MARC::Record;
use t::lib::Mocks;
@ -152,3 +152,11 @@ is( $entity[0], "Bj&#xC3;&#xB6;rn", "Html umlauts" );
$marc->field('260')->add_subfields( a => 'Hogwarts' );
my $cites = marc2cites($marc);
is( $cites->{'Chicago'}, 'Rowling J.K, . 2011. Harry potter. Hogwarts: Reprints.', 'testing marc2cites' );
my $field245 = $marc->field('245');
$field245->add_subfields( 'b' => 'the cursed child' );
$cites = marc2cites($marc);
is(
$cites->{'Chicago'}, 'Rowling J.K, . 2011. Harry potter: the cursed child. Hogwarts: Reprints.',
'testing marc2cites'
);

View File

@ -253,7 +253,15 @@ subtest 'get_saved_reports' => sub {
my $r1 = Koha::Reports->find( $report_ids[0] );
$r1 = update_sql(
$r1->id,
{ %{ $r1->unblessed }, borrowernumber => $r1->borrowernumber, name => 'Just another report' }
{
sql => $r1->savedsql,
name => 'Just another report',
notes => $r1->notes,
group => $r1->report_group,
subgroup => $r1->report_subgroup,
cache_expiry => $r1->cache_expiry,
public => $r1->public,
}
);
is( $r1->cache_expiry, 300, 'cache_expiry has the correct default value, from DBMS' );