From 5b117f43187c2f931aef05ede9dd3950799223e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johanna=20R=C3=A4is=C3=A4?= Date: Tue, 3 Feb 2026 14:55:12 +0200 Subject: [PATCH] Bug 41757: Copy framework defaults to ILL Standard backend This patch ensures that when a new ILL record is created using the Standard backend, the framework defaults are applied. Test plan: 1. Create a new ILL record using the Standard backend. 2. Verify that the framework defaults are correctly applied to the new record. 3. prove t/db_dependent/Koha/BiblioFrameworks.t Sponsored-by: Koha-Suomi Oy --- Koha/BiblioFramework.pm | 52 +++++++++ Koha/ILL/Backend/Standard.pm | 8 +- t/db_dependent/Koha/BiblioFrameworks.t | 152 ++++++++++++++++++++++++- 3 files changed, 209 insertions(+), 3 deletions(-) diff --git a/Koha/BiblioFramework.pm b/Koha/BiblioFramework.pm index 02d08c9832..bfb094cd08 100644 --- a/Koha/BiblioFramework.pm +++ b/Koha/BiblioFramework.pm @@ -18,6 +18,7 @@ package Koha::BiblioFramework; use Modern::Perl; use Koha::Database; +use C4::Biblio qw( GetMarcFromKohaField GetMarcStructure IsMarcStructureInternal ); use base qw(Koha::Object); @@ -31,6 +32,57 @@ Koha::BiblioFramework - Koha BiblioFramework Object class =cut +=head2 fill_with_default_values + +Fill the given MARC record with default values from the framework. If the +optional parameter C is set to a true value, only the mandatory +fields/subfields will be filled. + + $framework->fill_with_default_values( $record, { only_mandatory => 1 } ); + +Where C<$framework> is a L object and C<$record> is a +L object. + +=cut + +sub fill_with_default_values { + my ( $self, $record, $params ) = @_; + return unless $record; + my $tagslib = C4::Biblio::GetMarcStructure( 1, $self->frameworkcode, { unsafe => 1 } ); + my $mandatory = $params->{only_mandatory}; + if ($tagslib) { + my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber'); + for my $tag ( sort keys %$tagslib ) { + next unless $tag; + next if $tag == $itemfield; + for my $subfield ( sort keys %{ $tagslib->{$tag} } ) { + next if C4::Biblio::IsMarcStructureInternal( $tagslib->{$tag}{$subfield} ); + next if $mandatory && !$tagslib->{$tag}{$subfield}{mandatory}; + my $defaultvalue = $tagslib->{$tag}{$subfield}{defaultvalue}; + if ( defined $defaultvalue and $defaultvalue ne '' ) { + my @fields = $record->field($tag); + if (@fields) { + for my $field (@fields) { + if ( $field->is_control_field ) { + $field->update($defaultvalue) if not defined $field->data; + } elsif ( not defined $field->subfield($subfield) ) { + $field->add_subfields( $subfield => $defaultvalue ); + } + } + } else { + if ( $tag < 10 ) { # is_control_field + $record->insert_fields_ordered( MARC::Field->new( $tag, $defaultvalue ) ); + } else { + $record->insert_fields_ordered( + MARC::Field->new( $tag, '', '', $subfield => $defaultvalue ) ); + } + } + } + } + } + } +} + =head3 type =cut diff --git a/Koha/ILL/Backend/Standard.pm b/Koha/ILL/Backend/Standard.pm index 0e9602094b..c47f6e890d 100644 --- a/Koha/ILL/Backend/Standard.pm +++ b/Koha/ILL/Backend/Standard.pm @@ -26,7 +26,9 @@ use Koha::DateUtils qw/ dt_from_string /; use Koha::I18N qw(__); use Koha::ILL::Requests; use Koha::ILL::Request::Attribute; -use C4::Biblio qw( AddBiblio ); +use C4::Biblio qw( AddBiblio ); +use Koha::BiblioFramework; +use Koha::BiblioFrameworks; use C4::Charset qw( MarcToUTF8Record ); =head1 NAME @@ -1157,6 +1159,10 @@ sub _standard_request2biblio { # Suppress the record _set_suppression($record); + # Fill with default values from framework + my $framework = Koha::BiblioFrameworks->find( $self->{framework} ); + $framework->fill_with_default_values($record); + # Create a biblio record my ( $biblionumber, $biblioitemnumber ) = AddBiblio( $record, $self->{framework} ); diff --git a/t/db_dependent/Koha/BiblioFrameworks.t b/t/db_dependent/Koha/BiblioFrameworks.t index 57e1ea8f16..f1e0e77ff8 100755 --- a/t/db_dependent/Koha/BiblioFrameworks.t +++ b/t/db_dependent/Koha/BiblioFrameworks.t @@ -19,11 +19,19 @@ use Modern::Perl; use Test::NoWarnings; -use Test::More tests => 4; +use Test::More tests => 5; +use Test::MockModule; +use MARC::Record; +use MARC::Field; + use Koha::BiblioFramework; use Koha::BiblioFrameworks; +use Koha::Database; +use t::lib::TestBuilder; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; -my $schema = Koha::Database->new->schema; $schema->storage->txn_begin; my $nb_of_frameworks = Koha::BiblioFrameworks->search->count; @@ -50,4 +58,144 @@ is( $retrieved_framework_1->delete; is( Koha::BiblioFrameworks->search->count, $nb_of_frameworks + 1, 'Delete should have deleted the biblio framework' ); + +subtest 'fill_with_default_values' => sub { + plan tests => 14; + + # Create a test framework + my $framework = $builder->build_object( { class => 'Koha::BiblioFrameworks' } ); + my $frameworkcode = $framework->frameworkcode; + + my $default_008 = '######s################'; + my $default_author = 'Default Author'; + + my $biblio_module = Test::MockModule->new('C4::Biblio'); + $biblio_module->mock( + 'GetMarcStructure', + sub { + return { + # default for a control field + '008' => { + x => { defaultvalue => $default_008 }, + }, + + # default value for an existing field + '245' => { + c => { defaultvalue => $default_author, mandatory => 1 }, + mandatory => 0, + repeatable => 0, + tab => 0, + lib => 'a lib', + }, + + # default for a nonexisting field + '099' => { + x => { defaultvalue => 'default_099' }, + }, + '942' => { + c => { defaultvalue => 'BK', mandatory => 1 }, + d => { defaultvalue => '942d_val' }, + f => { defaultvalue => '942f_val' }, + }, + }; + } + ); + + # Mock GetMarcFromKohaField to return item field + $biblio_module->mock( 'GetMarcFromKohaField', sub { return ( '952', 'i' ); } ); + + # Mock IsMarcStructureInternal - return true for non-hashref (tag properties), false for subfields + $biblio_module->mock( + 'IsMarcStructureInternal', + sub { + my ($subfieldstruct) = @_; + return 1 unless ref($subfieldstruct) eq 'HASH'; + return 0; + } + ); + + # Test 1: Fill defaults for existing record + my $record = MARC::Record->new(); + $record->leader('03174nam a2200445 a 4500'); + my @fields = ( + MARC::Field->new( + '008', '120829t20132012nyu bk 001 0ceng', + ), + MARC::Field->new( + 100, '1', ' ', + a => 'Knuth, Donald Ervin', + d => '1938', + ), + MARC::Field->new( + 245, '1', '4', + a => 'The art of computer programming', + c => 'Donald E. Knuth.', + ), + MARC::Field->new( + 245, '1', '4', a => 'my second title', + ), + ); + + $record->append_fields(@fields); + $framework->fill_with_default_values($record); + + my @fields_245 = $record->field(245); + is( scalar(@fields_245), 2, 'No new 245 field has been created' ); + my @subfields_245_0 = $fields_245[0]->subfields; + my @subfields_245_1 = $fields_245[1]->subfields; + is_deeply( + \@subfields_245_0, + [ [ 'a', 'The art of computer programming' ], [ 'c', 'Donald E. Knuth.' ] ], + 'first 245 field has not been updated' + ); + is_deeply( + \@subfields_245_1, + [ [ 'a', 'my second title' ], [ 'c', $default_author ] ], + 'second 245 field has a new subfield c with a default value' + ); + + # Test 2: New field with default is created + my @fields_099 = $record->field('099'); + is( scalar(@fields_099), 1, '1 new 099 field has been created' ); + my @subfields_099 = $fields_099[0]->subfields; + is_deeply( + \@subfields_099, + [ [ 'x', 'default_099' ] ], + '099$x contains the default value' + ); + + # Test 3: Control field default is applied when undefined + $record->field('008')->update(undef); + $framework->fill_with_default_values($record); + is( $record->field('008')->data, $default_008, 'Controlfield got default' ); + + # Test 4: Check 942 fields + is( $record->subfield( '942', 'd' ), '942d_val', 'Check 942d' ); + + # Test 5: Only mandatory parameter + $record->delete_fields( $record->field('245') ); + $record->delete_fields( $record->field('942') ); + $record->append_fields( MARC::Field->new( '942', '', '', 'f' => 'f val' ) ); + + # We deleted 245 and replaced 942. If we only apply mandatories, we should get + # back 245c again and 942c but not 942d. 942f should be left alone. + $framework->fill_with_default_values( $record, { only_mandatory => 1 } ); + @fields_245 = $record->field(245); + is( scalar @fields_245, 1, 'Only one 245 expected' ); + is( $record->subfield( '245', 'c' ), $default_author, '245c restored' ); + is( $record->subfield( '942', 'c' ), 'BK', '942c also restored' ); + is( $record->subfield( '942', 'd' ), undef, '942d should not be there' ); + is( $record->subfield( '942', 'f' ), 'f val', '942f untouched' ); + + # Test 6: Handle undefined record gracefully + my $record_undef; + $framework->fill_with_default_values($record_undef); + ok( !defined $record_undef, 'Undefined record handled without error' ); + + # Test 7: Empty record should get defaults + my $empty_record = MARC::Record->new(); + $framework->fill_with_default_values($empty_record); + is( $empty_record->subfield( '942', 'c' ), 'BK', 'Empty record gets default 942c' ); +}; + $schema->storage->txn_rollback; -- 2.34.1