Bug 31925: AutoCreateAuthorities must preserve subject heading thesaurus

When AutoCreateAuthorities creates a new authority for a bib subject
heading that didn't match an existing one, it never set 008 or 040 on the
new MARC::Record, so C4::AuthoritiesMarc::AddAuthority()'s own defaulting
always ran and hardcoded 008/11 to 'a' (LCSH), regardless of the source
heading's real thesaurus. A FAST, MeSH, or other non-LCSH heading (MARC
indicator 2 = 7 with subfield $2, or indicator 2 = 1-6) therefore always
got mis-coded as LCSH on creation. Because the authority was permanently
mis-coded, it could never be found again by thesaurus-aware matching
(LinkerConsiderThesaurus), so every later import of the same non-LCSH
heading created yet another duplicate authority instead of linking to the
one already created (see bug 42694 for a real-world report of this).

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

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

Sponsored-by: Heythrop Library <https://heythroplibrary.co.uk/>
Signed-off-by: Clemens Gresser <cgresser@jesuit.org.uk>
Signed-off-by: Phil Ringnalda <phil@chetcolibrary.org>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This commit is contained in:
Martin Renvoize 2026-07-10 12:12:22 +01:00 committed by Pedro Amorim
parent 9186341ddd
commit 65dac0fb94
No known key found for this signature in database
GPG Key ID: 06B75BA7F3894D55
2 changed files with 90 additions and 7 deletions

View File

@ -99,6 +99,7 @@ use Koha::Logger;
use Koha::Cache::Memory::Lite;
use Koha::Caches;
use Koha::ClassSources;
use Koha::Authority;
use Koha::Authority::Types;
use Koha::Acquisition::Currencies;
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
@ -811,10 +812,35 @@ sub LinkBibHeadingsToAuthorities {
if ( C4::Context->preference('marcflavour') eq 'MARC21' ) {
my $userenv = C4::Context->userenv;
my $library;
if ( $userenv && $userenv->{'branch'} ) {
$library = Koha::Libraries->find( $userenv->{'branch'} );
# userenv's library could not exist because of a trick in misc/commit_file.pl
# (see FIXME and set_userenv statement there)
my $library =
$userenv && $userenv->{branch} ? Koha::Libraries->find( $userenv->{branch} ) : undef;
my $marcorgcode =
$library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode');
# Bug 31925: code the new authority with the source heading's actual
# thesaurus instead of relying on AddAuthority()'s LCSH-only default.
# AddAuthority() only defaults 008/040 when they aren't already present
# on the record, so pre-populating them here is sufficient - no change
# to AddAuthority() itself is needed.
if ( $heading->{thesaurus} ) {
my $date = POSIX::strftime( '%y%m%d', localtime );
$marcrecordauth->insert_fields_ordered(
MARC::Field->new(
'008', $date . Koha::Authority->default_marc21_008( $heading->{thesaurus} )
)
);
my @f040_subfields = ( 'a' => $marcorgcode, 'c' => $marcorgcode );
push @f040_subfields, ( 'f' => $heading->{thesaurus} )
unless exists
$Koha::Authority::MARC21_THESAURUS_TO_CONTROL_FIELD_008_11->{ $heading->{thesaurus} };
$marcrecordauth->insert_fields_ordered(
MARC::Field->new( '040', '', '', @f040_subfields ) );
}
$marcrecordauth->insert_fields_ordered(
MARC::Field->new(
'667', '', '',
@ -825,10 +851,8 @@ sub LinkBibHeadingsToAuthorities {
$cite =~ s/^[\s\,]*//;
$cite =~ s/[\s\,]*$//;
$cite =
C4::Context->preference('GenerateAuthorityField670') . ": ("
. (
$library ? $library->get_effective_marcorgcode : C4::Context->preference('MARCOrgCode') )
. ")"
C4::Context->preference('GenerateAuthorityField670') . ": ("
. "$marcorgcode)"
. $bib->subfield( '999', 'c' ) . ": "
. $cite;
$marcrecordauth->insert_fields_ordered( MARC::Field->new( '670', '', '', 'a' => $cite ) );

View File

@ -1114,6 +1114,65 @@ subtest "LinkBibHeadingsToAuthorities record generation tests" => sub {
);
};
subtest "LinkBibHeadingsToAuthorities codes new authorities with the source thesaurus (bug 31925)" => sub {
plan tests => 7;
my $biblio_mod = Test::MockModule->new('C4::Linker::Default');
$biblio_mod->mock(
'get_link',
sub {
return ( undef, undef, 0 );
}
);
t::lib::Mocks::mock_preference( 'marcflavour', 'MARC21' );
t::lib::Mocks::mock_preference( 'AutoCreateAuthorities', '1' );
my $linker = C4::Linker::Default->new();
# Case 1: plain LCSH (ind2=0, no $2) - regression guard, must stay coded 'a'
my $biblio = $builder->build_sample_biblio();
my $record = $biblio->metadata->record;
my $field = MARC::Field->new( '650', '', '0', 'a' => 'Feminism' );
$record->append_fields($field);
LinkBibHeadingsToAuthorities( $linker, $record, "", undef, 650 );
my $authority = GetAuthority( $record->subfield( '650', '9' ) );
is( substr( $authority->field('008')->data(), 11, 1 ), 'a', 'Plain LCSH heading (ind2=0) is coded 008/11=a' );
is( $authority->subfield( '040', 'f' ), undef, 'Plain LCSH heading (ind2=0) gets no 040$f' );
# Case 2: FAST heading (ind2=7, $2=fast) - the actual bug: must NOT be coded as LCSH
$biblio = $builder->build_sample_biblio();
$record = $biblio->metadata->record;
$field = MARC::Field->new( '650', '', '7', 'a' => 'Miracles', '2' => 'fast' );
$record->append_fields($field);
LinkBibHeadingsToAuthorities( $linker, $record, "", undef, 650 );
$authority = GetAuthority( $record->subfield( '650', '9' ) );
is( substr( $authority->field('008')->data(), 11, 1 ), 'z', 'FAST heading (ind2=7, $2=fast) is coded 008/11=z' );
is( $authority->subfield( '040', 'f' ), 'fast', 'FAST heading (ind2=7, $2=fast) gets 040$f=fast' );
# Case 3: MeSH heading (ind2=2, no $2)
$biblio = $builder->build_sample_biblio();
$record = $biblio->metadata->record;
$field = MARC::Field->new( '650', '', '2', 'a' => 'Something' );
$record->append_fields($field);
LinkBibHeadingsToAuthorities( $linker, $record, "", undef, 650 );
$authority = GetAuthority( $record->subfield( '650', '9' ) );
is( substr( $authority->field('008')->data(), 11, 1 ), 'c', 'MeSH heading (ind2=2) is coded 008/11=c' );
is( $authority->subfield( '040', 'f' ), undef, 'MeSH heading (ind2=2) gets no 040$f' );
# Case 4: non-subject heading (personal name added entry) is unaffected - no thesaurus semantics
$biblio = $builder->build_sample_biblio();
$record = $biblio->metadata->record;
$field = MARC::Field->new( '700', '', '', 'a' => 'Doe, Jane' );
$record->append_fields($field);
LinkBibHeadingsToAuthorities( $linker, $record, "", undef, 700 );
$authority = GetAuthority( $record->subfield( '700', '9' ) );
is(
substr( $authority->field('008')->data(), 11, 1 ), 'a',
'Non-subject heading (700) keeps the default LCSH 008/11=a coding, unaffected by this fix'
);
};
subtest 'autoControlNumber tests' => sub {
plan tests => 3;