Bugzilla – Attachment 189006 Details for
Bug 34198
Interface to define relational rules for MARC fields that use authorised values
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 34198: Add parent-child relationship to authorised values
74c2b43.patch (text/plain), 48.97 KB, created by
Julian Maurice
on 2025-11-04 09:24:30 UTC
(
hide
)
Description:
Bug 34198: Add parent-child relationship to authorised values
Filename:
MIME Type:
Creator:
Julian Maurice
Created:
2025-11-04 09:24:30 UTC
Size:
48.97 KB
patch
obsolete
>From 74c2b43f4af44a7ee1a2a857bfc89c47f25a925c Mon Sep 17 00:00:00 2001 >From: Julian Maurice <julian.maurice@biblibre.com> >Date: Thu, 30 Oct 2025 13:37:27 +0000 >Subject: [PATCH] Bug 34198: Add parent-child relationship to authorised values > >The goal is to have some kind of hierarchy between different authorised >value categories so that, when cataloging, selecting an authorised value >in a parent authorised value category allow only a subset of values for >child authorised value categories. > >This patch allows authorised value categories to have a parent category. >When an authorised value category have a parent, its authorised values >can have a parent authorised value in the parent category. > >An authorised value that have a parent can only be selected if: >- its parent authorised value have been selected, or >- no authorised value have been selected in the parent category > >An authorised value that have no parent can always be selected. > >This only works within the context of a MARC field. Selecting a value >for a subfield will only alter subfields of the same field. > >A child category can also be a parent category. There is no limit to the >depth of this hierarchy. > >Test plan: >1. Create an authorised value category TYPE with the following values: > - Type 1 > - Type 2 > - Type 3 >2. Create an authorised value category SUBTYPE (parent: TYPE) with the > following values: > - Subtype 1.1 (parent: Type 1) > - Subtype 1.2 (parent: Type 1) > - Subtype 2.1 (parent: Type 2) > - Subtype 2.2 (parent: Type 2) > - Subtype X.1 (no parent) > - Subtype X.2 (no parent) >3. Create an authorised value category SUBSUBTYPE (parent: SUBTYPE) with > the following values: > - Subsubtype 1.1.1 (parent Subtype 1.1) > - Subsubtype 2.2.1 (parent Subtype 2.2) > - Subsubtype Y (no parent) >4. In the default MARC framework, create a 900 field. Within this field, > - create a subfield 'a' linked to AV category TYPE, > - create a subfield 'b' linked to AV category SUBTYPE, > - create a subfield 'c' linked to AV category SUBSUBTYPE. >5. In the default MARC framework, in the item MARC field (952 or 995), > edit or create 3 subfields to link them to these AV categories. > Do the same for ACQ framework. >6. Do the same thing again for an authority type >7. Edit or create a bibliographic record. Go to field 900 and select a > value for $a then open $b dropdown list to verify that values are > disabled correctly. For instance, if $a = 'Type 1', 'Subtype 2.1' and > 'Subtype 2.2' should be disabled. > Select a value for $b and open $c dropdown list to verify that values > are disabled correctly. > Clone a field and verify that this works in the cloned field too. >8. Repeat step 7 for the item editor >9. Repeat step 7 for the item editor in acquisitions >10. Repeat step 7 for the authority editor >--- > C4/Items.pm | 24 +++++-- > Koha/AuthorisedValue.pm | 15 +++++ > Koha/AuthorisedValueCategory.pm | 15 +++++ > Koha/UI/Form/Builder/Biblio.pm | 32 +++++++--- > Koha/UI/Form/Builder/Item.pm | 33 +++++----- > admin/authorised_values.pl | 63 ++++++++++++------- > .../swagger/definitions/authorised_value.yaml | 6 ++ > .../authorised_value_category.yaml | 6 ++ > authorities/authorities.pl | 33 +++++++--- > .../data/mysql/atomicupdate/bug-34198.pl | 36 +++++++++++ > installer/data/mysql/kohastructure.sql | 8 ++- > .../prog/en/includes/html_helpers.inc | 13 ++-- > .../en/modules/admin/authorised_values.tt | 33 ++++++++++ > .../en/modules/authorities/authorities.tt | 8 ++- > .../prog/en/modules/cataloguing/addbiblio.tt | 10 +-- > .../en/modules/services/itemrecorddisplay.tt | 8 ++- > koha-tmpl/intranet-tmpl/prog/js/cataloging.js | 33 ++++++++++ > 17 files changed, 295 insertions(+), 81 deletions(-) > create mode 100755 installer/data/mysql/atomicupdate/bug-34198.pl > >diff --git a/C4/Items.pm b/C4/Items.pm >index 9d426169a26..9ccb0c52b3a 100644 >--- a/C4/Items.pm >+++ b/C4/Items.pm >@@ -59,6 +59,7 @@ use DateTime::Format::MySQL; > # debugging; so please don't remove this > use Try::Tiny qw( catch try ); > >+use Koha::AuthorisedValueCategories; > use Koha::AuthorisedValues; > use Koha::DateUtils qw( dt_from_string ); > use Koha::Database; >@@ -1377,7 +1378,7 @@ sub PrepareItemrecordDisplay { > > my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : ""; > my $query = qq{ >- SELECT authorised_value,lib FROM authorised_values >+ SELECT id, authorised_value, parent_authorised_value_id, lib FROM authorised_values > }; > $query .= qq{ > LEFT JOIN authorised_values_branches ON ( id = av_id ) >@@ -1513,6 +1514,7 @@ sub PrepareItemrecordDisplay { > if ( $subfield->{authorised_value} ) { > my @authorised_values; > my %authorised_lib; >+ my %attributes; > > # builds list, depending on authorised value... > #---- branch >@@ -1581,16 +1583,26 @@ sub PrepareItemrecordDisplay { > $branch_limit ? $branch_limit : () > ); > push @authorised_values, ""; >- while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) { >+ while ( my ( $id, $value, $parent_authorised_value_id, $lib ) = >+ $authorised_values_sth->fetchrow_array ) >+ { > push @authorised_values, $value; > $authorised_lib{$value} = $lib; >+ $attributes{$value} = { >+ 'data-id' => $id, >+ 'data-parent-authorised-value-id' => $parent_authorised_value_id, >+ }; > } > } > $subfield_data{marc_value} = { >- type => 'select', >- values => \@authorised_values, >- default => $defaultvalue // q{}, >- labels => \%authorised_lib, >+ type => 'select', >+ values => \@authorised_values, >+ default => $defaultvalue // q{}, >+ labels => \%authorised_lib, >+ attributes => \%attributes, >+ category => $subfield->{authorised_value}, >+ authorised_value_category => >+ Koha::AuthorisedValueCategories->find( $subfield->{authorised_value} ), > }; > } elsif ( $subfield->{value_builder} ) { > >diff --git a/Koha/AuthorisedValue.pm b/Koha/AuthorisedValue.pm >index d52110504bb..5b0c17c9b1b 100644 >--- a/Koha/AuthorisedValue.pm >+++ b/Koha/AuthorisedValue.pm >@@ -131,6 +131,21 @@ sub is_integer_only { > return $self->_result->category->is_integer_only; > } > >+=head3 parent_authorised_value >+ >+Return the parent authorised value (C<Koha::AuthorisedValue> object), or undef >+ >+ my $parent_authorised_value = $authorised_value->parent_authorised_value(); >+ >+=cut >+ >+sub parent_authorised_value { >+ my ($self) = @_; >+ >+ my $parent_authorised_value = $self->_result->parent_authorised_value; >+ return $parent_authorised_value ? Koha::AuthorisedValue->_new_from_dbic($parent_authorised_value) : undef; >+} >+ > =head3 to_api > > my $json = $av->to_api; >diff --git a/Koha/AuthorisedValueCategory.pm b/Koha/AuthorisedValueCategory.pm >index 6264c81d351..1de470fec91 100644 >--- a/Koha/AuthorisedValueCategory.pm >+++ b/Koha/AuthorisedValueCategory.pm >@@ -45,6 +45,21 @@ sub authorised_values { > return Koha::AuthorisedValues->_new_from_dbic($authorised_values_rs); > } > >+=head3 parent_category >+ >+Return the parent authorised value category (C<Koha::AuthorisedValueCategory> object), or undef >+ >+ my $parent_category = $authorised_value_category->parent_category(); >+ >+=cut >+ >+sub parent_category { >+ my ($self) = @_; >+ >+ my $parent_category = $self->_result->parent_category_name; >+ return $parent_category ? Koha::AuthorisedValueCategory->_new_from_dbic($parent_category) : undef; >+} >+ > =head3 delete > > Overridden delete method to prevent system default deletions >diff --git a/Koha/UI/Form/Builder/Biblio.pm b/Koha/UI/Form/Builder/Biblio.pm >index a54ac0c4b7c..8278f0379d4 100644 >--- a/Koha/UI/Form/Builder/Biblio.pm >+++ b/Koha/UI/Form/Builder/Biblio.pm >@@ -20,6 +20,7 @@ use feature qw(fc); > > use C4::Context; > use C4::ClassSource qw( GetClassSources ); >+use Koha::AuthorisedValueCategories; > use Koha::DateUtils qw( dt_from_string ); > use Koha::ItemTypes; > use Koha::Libraries; >@@ -312,6 +313,7 @@ sub build_authorized_values_list { > > my @authorised_values; > my %authorised_lib; >+ my %attributes; > > # builds list, depending on authorised value... > >@@ -351,7 +353,7 @@ sub build_authorized_values_list { > $value = $default_source unless $value; > } else { > my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{branch} : ''; >- my $query = 'SELECT authorised_value, lib FROM authorised_values'; >+ my $query = 'SELECT id, authorised_value, parent_authorised_value_id, lib FROM authorised_values'; > $query .= ' LEFT JOIN authorised_values_branches ON ( id = av_id )' if $branch_limit; > $query .= ' WHERE category = ?'; > $query .= ' AND ( branchcode = ? OR branchcode IS NULL )' if $branch_limit; >@@ -365,25 +367,35 @@ sub build_authorized_values_list { > > push @authorised_values, ""; > >- while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) { >+ while ( my ( $id, $value, $parent_authorised_value_id, $lib ) = $authorised_values_sth->fetchrow_array ) { > push @authorised_values, $value; > $authorised_lib{$value} = $lib; >+ $attributes{$value} = { >+ 'data-id' => $id, >+ 'data-parent-authorised-value-id' => $parent_authorised_value_id, >+ }; > } > } > > my $id_subfield = $subfield; > $id_subfield = "00" if $id_subfield eq "@"; > >- return { >- type => 'select', >- id => "tag_" . $tag . "_subfield_" . $id_subfield . "_" . $index_tag . "_" . $index_subfield, >- name => "tag_" . $tag . "_subfield_" . $id_subfield . "_" . $index_tag . "_" . $index_subfield, >- default => $value, >- values => \@authorised_values, >- labels => \%authorised_lib, >- ( ( grep { $_ eq $category } (qw(branches itemtypes cn_source)) ) ? () : ( category => $category ) ), >+ my $res = { >+ type => 'select', >+ id => "tag_" . $tag . "_subfield_" . $id_subfield . "_" . $index_tag . "_" . $index_subfield, >+ name => "tag_" . $tag . "_subfield_" . $id_subfield . "_" . $index_tag . "_" . $index_subfield, >+ default => $value, >+ values => \@authorised_values, >+ labels => \%authorised_lib, >+ attributes => \%attributes, > }; > >+ unless ( grep { $_ eq $category } (qw(branches itemtypes cn_source)) ) { >+ $res->{category} = $category; >+ $res->{authorised_value_category} = Koha::AuthorisedValueCategories->find($category); >+ } >+ >+ return $res; > } > > =head3 create_key >diff --git a/Koha/UI/Form/Builder/Item.pm b/Koha/UI/Form/Builder/Item.pm >index c43483895df..77f5180cd26 100644 >--- a/Koha/UI/Form/Builder/Item.pm >+++ b/Koha/UI/Form/Builder/Item.pm >@@ -25,6 +25,7 @@ use C4::Biblio qw( GetFrameworkCode GetMarcStructure IsMarcStructureInterna > use C4::Koha qw( GetAuthorisedValues ); > use C4::ClassSource qw( GetClassSources ); > >+use Koha::AuthorisedValueCategories; > use Koha::Biblios; > use Koha::DateUtils qw( dt_from_string ); > use Koha::Libraries; >@@ -169,6 +170,7 @@ sub generate_subfield_form { > if ( $subfield->{authorised_value} ) { > my @authorised_values; > my %authorised_lib; >+ my %attributes; > > # builds list, depending on authorised value... > if ( $subfield->{authorised_value} eq "LOST" ) { >@@ -249,6 +251,10 @@ sub generate_subfield_form { > for my $r (@$av) { > push @authorised_values, $r->{authorised_value}; > $authorised_lib{ $r->{authorised_value} } = $r->{lib}; >+ $attributes{ $r->{authorised_value} } = { >+ 'data-id' => $r->{id}, >+ 'data-parent-authorised-value-id' => $r->{parent_authorised_value_id}, >+ }; > } > } > >@@ -258,26 +264,23 @@ sub generate_subfield_form { > id => $subfield_data{id}, > maxlength => $subfield_data{maxlength}, > value => $value, >- ( >- ( grep { $_ eq $subfield->{authorised_value} } (qw(branches itemtypes cn_source)) ) >- ? () >- : ( category => $subfield->{authorised_value} ) >- ), > }; > } else { > $subfield_data{marc_value} = { >- type => 'select', >- id => "tag_" . $tag . "_subfield_" . $subfieldtag . "_" . $index_subfield, >- values => \@authorised_values, >- labels => \%authorised_lib, >- default => $value, >- ( >- ( grep { $_ eq $subfield->{authorised_value} } (qw(branches itemtypes cn_source)) ) >- ? () >- : ( category => $subfield->{authorised_value} ) >- ), >+ type => 'select', >+ id => "tag_" . $tag . "_subfield_" . $subfieldtag . "_" . $index_subfield, >+ values => \@authorised_values, >+ labels => \%authorised_lib, >+ attributes => \%attributes, >+ default => $value, > }; > } >+ >+ unless ( grep { $_ eq $subfield->{authorised_value} } (qw(branches itemtypes cn_source)) ) { >+ $subfield_data{marc_value}->{category} = $subfield->{authorised_value}; >+ $subfield_data{marc_value}->{authorised_value_category} = >+ Koha::AuthorisedValueCategories->find( $subfield->{authorised_value} ); >+ } > } > > # it's a thesaurus / authority field >diff --git a/admin/authorised_values.pl b/admin/authorised_values.pl >index e1063387037..4a61eb167a0 100755 >--- a/admin/authorised_values.pl >+++ b/admin/authorised_values.pl >@@ -80,6 +80,7 @@ if ( $op eq 'add_form' or $op eq 'edit_form' ) { > > if ($av) { > $template->param( >+ category => Koha::AuthorisedValueCategories->find( $av->category ), > category_name => $av->category, > av => $av, > imagesets => C4::Koha::getImageSets( checked => $av->imageurl ), >@@ -97,9 +98,10 @@ if ( $op eq 'add_form' or $op eq 'edit_form' ) { > ); > > } elsif ( $op eq 'cud-add' ) { >- my $new_authorised_value = $input->param('authorised_value'); >- my $new_category = $input->param('category'); >- my $image = $input->param('image') || ''; >+ my $new_authorised_value = $input->param('authorised_value'); >+ my $parent_authorised_value_id = $input->param('parent_authorised_value_id') // ''; >+ my $new_category = $input->param('category'); >+ my $image = $input->param('image') || ''; > my $imageurl = > $image eq 'removeImage' ? '' > : ( >@@ -118,6 +120,7 @@ if ( $op eq 'add_form' or $op eq 'edit_form' ) { > $av->lib_opac( scalar $input->param('lib_opac') || undef ); > $av->category($new_category); > $av->authorised_value($new_authorised_value); >+ $av->parent_authorised_value_id( $parent_authorised_value_id || undef ); > $av->imageurl($imageurl); > eval { > $av->store; >@@ -132,11 +135,12 @@ if ( $op eq 'add_form' or $op eq 'edit_form' ) { > eval { > my $av = Koha::AuthorisedValue->new( > { >- category => $new_category, >- authorised_value => $new_authorised_value, >- lib => scalar $input->param('lib') || undef, >- lib_opac => scalar $input->param('lib_opac') || undef, >- imageurl => $imageurl, >+ category => $new_category, >+ authorised_value => $new_authorised_value, >+ parent_authorised_value_id => $parent_authorised_value_id || undef, >+ lib => scalar $input->param('lib') || undef, >+ lib_opac => scalar $input->param('lib_opac') || undef, >+ imageurl => $imageurl, > } > )->store; > $av->replace_library_limits( \@branches ); >@@ -153,8 +157,9 @@ if ( $op eq 'add_form' or $op eq 'edit_form' ) { > $op = 'list'; > $searchfield = $new_category; > } elsif ( $op eq 'cud-add_category' ) { >- my $new_category = $input->param('category'); >- my $is_integer_only = $input->param('is_integer_only') ? 1 : 0; >+ my $new_category = $input->param('category'); >+ my $parent_category_name = $input->param('parent_category_name'); >+ my $is_integer_only = $input->param('is_integer_only') ? 1 : 0; > > my $already_exists = Koha::AuthorisedValueCategories->find( > { >@@ -171,7 +176,9 @@ if ( $op eq 'add_form' or $op eq 'edit_form' ) { > } else { # Insert > my $av = Koha::AuthorisedValueCategory->new( > { >- category_name => $new_category, is_integer_only => $is_integer_only, >+ category_name => $new_category, >+ parent_category_name => $parent_category_name, >+ is_integer_only => $is_integer_only, > } > ); > >@@ -187,12 +194,21 @@ if ( $op eq 'add_form' or $op eq 'edit_form' ) { > > $op = 'list'; > } elsif ( $op eq 'cud-edit_category' ) { >- my $category_name = $input->param('category'); >- my $is_integer_only = $input->param('is_integer_only') ? 1 : 0; >- my $category = Koha::AuthorisedValueCategories->find($category_name); >+ my $category_name = $input->param('category'); >+ my $parent_category_name = $input->param('parent_category_name') // ''; >+ my $is_integer_only = $input->param('is_integer_only') ? 1 : 0; >+ my $category = Koha::AuthorisedValueCategories->find($category_name); > > if ($category) { >- $category->is_integer_only($is_integer_only)->store; >+ my $old_parent_category_name = $category->parent_category_name // ''; >+ >+ $category->parent_category_name( $parent_category_name ? $parent_category_name : undef ); >+ $category->is_integer_only($is_integer_only); >+ $category->store; >+ >+ if ( $old_parent_category_name ne $parent_category_name ) { >+ $category->authorised_values->update( { parent_authorised_value_id => undef } ); >+ } > } else { > push @messages, { type => 'error', code => 'error_on_edit_cat' }; > } >@@ -246,14 +262,15 @@ if ( $op eq 'list' ) { > # builds value list > for my $av (@avs_by_category) { > my %row_data; # get a fresh hash for the row data >- $row_data{category} = $av->category; >- $row_data{authorised_value} = $av->authorised_value; >- $row_data{lib} = $av->lib; >- $row_data{lib_opac} = $av->lib_opac; >- $row_data{image} = getitemtypeimagelocation( 'intranet', $av->imageurl ); >- $row_data{branches} = $av->library_limits ? $av->library_limits->as_list : []; >- $row_data{id} = $av->id; >- $row_data{is_integer_only} = $is_integer_only; >+ $row_data{category} = $av->category; >+ $row_data{authorised_value} = $av->authorised_value; >+ $row_data{parent_authorised_value} = $av->parent_authorised_value; >+ $row_data{lib} = $av->lib; >+ $row_data{lib_opac} = $av->lib_opac; >+ $row_data{image} = getitemtypeimagelocation( 'intranet', $av->imageurl ); >+ $row_data{branches} = $av->library_limits ? $av->library_limits->as_list : []; >+ $row_data{id} = $av->id; >+ $row_data{is_integer_only} = $is_integer_only; > push( @loop_data, \%row_data ); > } > >diff --git a/api/v1/swagger/definitions/authorised_value.yaml b/api/v1/swagger/definitions/authorised_value.yaml >index 0f11eb36b94..a3e9612278d 100644 >--- a/api/v1/swagger/definitions/authorised_value.yaml >+++ b/api/v1/swagger/definitions/authorised_value.yaml >@@ -5,6 +5,12 @@ properties: > type: integer > description: internally assigned authorised value identifier > readOnly: true >+ parent_authorised_value_id: >+ type: >+ - integer >+ - "null" >+ description: id of parent authorised value >+ readOnly: true > category_name: > description: the category of this authorised value > type: string >diff --git a/api/v1/swagger/definitions/authorised_value_category.yaml b/api/v1/swagger/definitions/authorised_value_category.yaml >index c7ecc5e8fd2..0c8a210c0c5 100644 >--- a/api/v1/swagger/definitions/authorised_value_category.yaml >+++ b/api/v1/swagger/definitions/authorised_value_category.yaml >@@ -12,6 +12,12 @@ properties: > is_integer_only: > description: Is this category integer only or not > readOnly: true >+ parent_category_name: >+ type: >+ - string >+ - "null" >+ description: Name of parent authorised value category >+ readOnly: true > authorised_values: > type: array > description: This category's authorised values >diff --git a/authorities/authorities.pl b/authorities/authorities.pl >index 592fc682742..241a600022a 100755 >--- a/authorities/authorities.pl >+++ b/authorities/authorities.pl >@@ -30,6 +30,7 @@ use Date::Calc qw( Today ); > use MARC::File::USMARC; > use MARC::File::XML; > use C4::Biblio qw( TransformHtmlToMarc ); >+use Koha::AuthorisedValueCategories; > use Koha::Authority::Types; > use Koha::Import::Records; > use Koha::ItemTypes; >@@ -54,6 +55,7 @@ sub build_authorized_values_list { > > my @authorised_values; > my %authorised_lib; >+ my %attributes; > > my $category = $tagslib->{$tag}->{$subfield}->{'authorised_value'}; > push @authorised_values, q{} unless $tagslib->{$tag}->{$subfield}->{mandatory} && $value; >@@ -73,21 +75,32 @@ sub build_authorized_values_list { > } > } else { # "true" authorised value > $authorised_values_sth->execute( $tagslib->{$tag}->{$subfield}->{authorised_value} ); >- while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) { >+ while ( my ( $id, $value, $parent_authorised_value_id, $lib ) = $authorised_values_sth->fetchrow_array ) { > push @authorised_values, $value; > $authorised_lib{$value} = $lib; >+ $attributes{$value} = { >+ 'data-id' => $id, >+ 'data-parent-authorised-value-id' => $parent_authorised_value_id, >+ }; > } > } > >- return { >- type => 'select', >- id => "tag_" . $tag . "_subfield_" . $subfield . "_" . $index_tag . "_" . $index_subfield, >- name => "tag_" . $tag . "_subfield_" . $subfield . "_" . $index_tag . "_" . $index_subfield, >- values => \@authorised_values, >- labels => \%authorised_lib, >- default => $value, >- ( ( grep { $_ eq $category } (qw(branches itemtypes cn_source)) ) ? () : ( category => $category ) ), >+ my $res = { >+ type => 'select', >+ id => "tag_" . $tag . "_subfield_" . $subfield . "_" . $index_tag . "_" . $index_subfield, >+ name => "tag_" . $tag . "_subfield_" . $subfield . "_" . $index_tag . "_" . $index_subfield, >+ values => \@authorised_values, >+ labels => \%authorised_lib, >+ attributes => \%attributes, >+ default => $value, > }; >+ >+ unless ( grep { $_ eq $category } (qw(branches itemtypes cn_source)) ) { >+ $res->{category} = $category; >+ $res->{authorised_value_category} = Koha::AuthorisedValueCategories->find($category); >+ } >+ >+ return $res; > } > > =item create_input >@@ -329,7 +342,7 @@ sub build_tabs { > my $tag; > > my $authorised_values_sth = $dbh->prepare( >- "SELECT authorised_value,lib >+ "SELECT id, authorised_value, parent_authorised_value_id, lib > FROM authorised_values > WHERE category=? ORDER BY lib" > ); >diff --git a/installer/data/mysql/atomicupdate/bug-34198.pl b/installer/data/mysql/atomicupdate/bug-34198.pl >new file mode 100755 >index 00000000000..2d47ec37170 >--- /dev/null >+++ b/installer/data/mysql/atomicupdate/bug-34198.pl >@@ -0,0 +1,36 @@ >+use Modern::Perl; >+use Koha::Installer::Output qw(say_warning say_success say_info); >+ >+return { >+ bug_number => "34198", >+ description => >+ "Add authorised_value_categories.parent_category_name and authorised_values.parent_authorised_value_id", >+ up => sub { >+ my ($args) = @_; >+ my ( $dbh, $out ) = @$args{qw(dbh out)}; >+ >+ unless ( column_exists( 'authorised_value_categories', 'parent_category_name' ) ) { >+ $dbh->do( >+ q{ >+ ALTER TABLE `authorised_value_categories` >+ ADD COLUMN `parent_category_name` varchar(32) NULL DEFAULT NULL COMMENT 'Name (primary key) of parent authorised value category' AFTER `category_name`, >+ ADD CONSTRAINT `fk_authorised_value_categories_parent_category_name` FOREIGN KEY (`parent_category_name`) REFERENCES `authorised_value_categories` (`category_name`) ON DELETE SET NULL ON UPDATE CASCADE >+ } >+ ); >+ >+ say $out "Added authorised_value_categories.parent_category_name and corresponding foreign key"; >+ } >+ >+ unless ( column_exists( 'authorised_values', 'parent_authorised_value_id' ) ) { >+ $dbh->do( >+ q{ >+ ALTER TABLE `authorised_values` >+ ADD COLUMN `parent_authorised_value_id` int(11) NULL DEFAULT NULL COMMENT 'id of parent authorised value' AFTER `authorised_value`, >+ ADD CONSTRAINT `fk_authorised_values_parent_authorised_value_id` FOREIGN KEY (`parent_authorised_value_id`) REFERENCES `authorised_values` (`id`) ON DELETE SET NULL ON UPDATE CASCADE >+ } >+ ); >+ >+ say $out "Added authorised_values.parent_authorised_value_id and corresponding foreign key"; >+ } >+ }, >+}; >diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql >index 998007871e2..6beec8b7b68 100644 >--- a/installer/data/mysql/kohastructure.sql >+++ b/installer/data/mysql/kohastructure.sql >@@ -1013,9 +1013,11 @@ DROP TABLE IF EXISTS `authorised_value_categories`; > /*!40101 SET character_set_client = utf8mb4 */; > CREATE TABLE `authorised_value_categories` ( > `category_name` varchar(32) NOT NULL DEFAULT '', >+ `parent_category_name` varchar(32) NULL DEFAULT NULL COMMENT 'Name (primary key) of parent authorised value category', > `is_system` tinyint(1) DEFAULT 0, > `is_integer_only` tinyint(1) NOT NULL DEFAULT 0, >- PRIMARY KEY (`category_name`) >+ PRIMARY KEY (`category_name`), >+ CONSTRAINT `fk_authorised_value_categories_parent_category_name` FOREIGN KEY (`parent_category_name`) REFERENCES `authorised_value_categories` (`category_name`) ON DELETE SET NULL ON UPDATE CASCADE > ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; > /*!40101 SET character_set_client = @saved_cs_client */; > >@@ -1030,6 +1032,7 @@ CREATE TABLE `authorised_values` ( > `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'unique key, used to identify the authorized value', > `category` varchar(32) NOT NULL DEFAULT '' COMMENT 'key used to identify the authorized value category', > `authorised_value` varchar(80) NOT NULL DEFAULT '' COMMENT 'code use to identify the authorized value', >+ `parent_authorised_value_id` int(11) NULL DEFAULT NULL COMMENT 'id of parent authorised value', > `lib` varchar(200) DEFAULT NULL COMMENT 'authorized value description as printed in the staff interface', > `lib_opac` varchar(200) DEFAULT NULL COMMENT 'authorized value description as printed in the OPAC', > `imageurl` varchar(200) DEFAULT NULL COMMENT 'authorized value URL', >@@ -1038,7 +1041,8 @@ CREATE TABLE `authorised_values` ( > KEY `name` (`category`), > KEY `lib` (`lib`(191)), > KEY `auth_value_idx` (`authorised_value`), >- CONSTRAINT `authorised_values_authorised_values_category` FOREIGN KEY (`category`) REFERENCES `authorised_value_categories` (`category_name`) ON DELETE CASCADE ON UPDATE CASCADE >+ CONSTRAINT `authorised_values_authorised_values_category` FOREIGN KEY (`category`) REFERENCES `authorised_value_categories` (`category_name`) ON DELETE CASCADE ON UPDATE CASCADE, >+ CONSTRAINT `fk_authorised_values_parent_authorised_value_id` FOREIGN KEY (`parent_authorised_value_id`) REFERENCES `authorised_values` (`id`) ON DELETE SET NULL ON UPDATE CASCADE > ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; > /*!40101 SET character_set_client = @saved_cs_client */; > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc >index be0af14ce17..864978e0b5b 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc >@@ -96,6 +96,7 @@ > [%- END %] > > [% BLOCK subfields_for_item %] >+ [% USE HTML %] > <ol> > [% FOREACH subfield IN subfields %] > [% IF subfield.kohafield == 'items.more_subfields_xml' %] >@@ -133,26 +134,28 @@ > [% select_readonly | html %] > [% select_disabled | html %] > data-category="[% mv.category | html %]" >+ data-parent-category="[% mv.authorised_value_category.parent_category_name | html %]" > data-width="50%" > > > [% SET matched = 0 %] > [% FOREACH aval IN mv.values %] >+ [% attributes = HTML.attributes(mv.attributes.$aval) %] > [% IF aval == mv.default %] > [% SET matched = 1 %] >- <option value="[%- aval | html -%]" selected="selected">[%- mv.labels.$aval | html -%]</option> >+ <option value="[%- aval | html -%]" selected="selected" [% attributes | $raw %]>[%- mv.labels.$aval | html -%]</option> > [% ELSE %] > [% IF subfield.IS_LOST_AV && Koha.Preference("ClaimReturnedLostValue") && aval == Koha.Preference("ClaimReturnedLostValue") %] >- <option disabled="disabled" value="[%- aval | html -%]" title="Return claims must be processed from the patron details page">[%- mv.labels.$aval | html -%]</option> >+ <option disabled="disabled" value="[%- aval | html -%]" title="Return claims must be processed from the patron details page" [% attributes | $raw %]>[%- mv.labels.$aval | html -%]</option> > [% ELSIF subfield.IS_LOST_AV && Koha.Preference("BundleLostValue") && aval == Koha.Preference("BundleLostValue") %] >- <option disabled="disabled" value="[%- aval | html -%]" title="Bundle losses are set at checkin automatically">[%- mv.labels.$aval | html -%]</option> >+ <option disabled="disabled" value="[%- aval | html -%]" title="Bundle losses are set at checkin automatically" [% attributes | $raw %]>[%- mv.labels.$aval | html -%]</option> > [% ELSE %] >- <option value="[%- aval | html -%]">[%- mv.labels.$aval | html -%]</option> >+ <option value="[%- aval | html -%]" [% attributes | $raw %]>[%- mv.labels.$aval | html -%]</option> > [% END %] > [% END %] > [% END %] > [% UNLESS matched || ( ( kohafield == 'items.damaged' || kohafield == 'items.itemlost' || kohafield == 'items.withdrawn' || kohafield == 'items.notforloan' ) && mv.default == '0' ) %] > [%# If the current value is not in the authorised list and is not a field where 0 means unset #%] >- <option value="[%- mv.default | html -%]" selected="selected">[%- mv.default | html -%] (Not an authorised value)</option> >+ <option value="[%- mv.default | html -%]" selected="selected" [% attributes | $raw %]>[%- mv.default | html -%] (Not an authorised value)</option> > [% END %] > </select> > [% UNLESS matched || ( ( kohafield == 'items.damaged' || kohafield == 'items.itemlost' || kohafield == 'items.withdrawn' || kohafield == 'items.notforloan' ) && mv.default == '0' ) %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/authorised_values.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/authorised_values.tt >index 05384025ba3..a388d89e8fd 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/authorised_values.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/authorised_values.tt >@@ -1,7 +1,9 @@ > [% USE raw %] > [% USE Koha %] > [% USE Asset %] >+[% USE AuthorisedValues %] > [% PROCESS 'i18n.inc' %] >+[% PROCESS 'html_helpers.inc' %] > [% SET footerjs = 1 %] > [% INCLUDE 'doc-head-open.inc' %] > <title >@@ -138,6 +140,13 @@ > <span class="required">Required</span> > <input type="hidden" name="op" value="cud-add_category" /> > </li> >+ <li> >+ <label for="parent_category_name">Parent category:</label> >+ <select id="parent_category_name" name="parent_category_name"> >+ <option value=""></option> >+ [% PROCESS options_for_authorised_value_categories authorised_value_categories => AuthorisedValues.GetCategories() %] >+ </select> >+ </li> > <li> <input type="checkbox" name="is_integer_only" id="is_integer_only" /><label for="is_integer_only">Restrict value to numbers only</label> </li> > </ol> > [% ELSIF op == 'edit_form' %] >@@ -146,6 +155,13 @@ > <label for="category" class="required">Category: </label> > <input type="text" disabled value="[% category_name | html %]" /> > </li> >+ <li> >+ <label for="parent_category_name">Parent category:</label> >+ <select id="parent_category_name" name="parent_category_name"> >+ <option value=""></option> >+ [% PROCESS options_for_authorised_value_categories authorised_value_categories => AuthorisedValues.GetCategories( selected => category.parent_category_name ) %] >+ </select> >+ </li> > <li> > [% IF category.is_integer_only %] > <input type="checkbox" checked name="is_integer_only" id="is_integer_only" /><label for="is_integer_only">Restrict value to numbers only</label> >@@ -187,6 +203,21 @@ > <input type="text" id="authorised_value" name="authorised_value" value="[% av.authorised_value | html %]" maxlength="80" class="focus" /> > [% END %] > </li> >+ >+ [% parent_category = category.parent_category %] >+ [% IF parent_category %] >+ <li> >+ <label for="parent_authorised_value_id">Parent authorised value: </label> >+ <select id="parent_authorised_value_id" name="parent_authorised_value_id"> >+ <option value=""></option> >+ [% FOREACH authorised_value IN parent_category.authorised_values %] >+ [% selected = authorised_value.id == av.parent_authorised_value_id %] >+ <option value="[% authorised_value.id | html %]" [% IF selected %]selected[% END %]>[% authorised_value.lib | html %]</option> >+ [% END %] >+ </select> >+ </li> >+ [% END %] >+ > <li> > <label for="lib">Description: </label> > <input type="text" name="lib" id="lib" value="[% av.lib | html %]" maxlength="200" /> >@@ -304,6 +335,7 @@ > <thead> > <tr> > <th>Authorized value</th> >+ [% IF category.parent_category_name %]<th>Parent authorised value</th>[% END %] > <th>Description</th> > <th>Description (OPAC)</th> > <th>Icon</th> >@@ -315,6 +347,7 @@ > [% FOREACH loo IN loop %] > <tr> > <td>[% loo.authorised_value | html %]</td> >+ [% IF category.parent_category_name %]<td>[% loo.parent_authorised_value.lib | html %]</td>[% END %] > <td>[% loo.lib | html %]</td> > <td>[% loo.lib_opac | html %]</td> > <td> [% IF ( loo.image ) %]<img class="itemtype-image" src="[% loo.image | url %]" alt="" />[% ELSE %] [% END %]</td> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/authorities/authorities.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/authorities/authorities.tt >index d9e91e55c03..3fb77307ee6 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/authorities/authorities.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/authorities/authorities.tt >@@ -2,6 +2,7 @@ > [% USE Koha %] > [% USE To %] > [% USE Asset %] >+[% USE HTML %] > [% PROCESS 'i18n.inc' %] > [% INCLUDE 'doc-head-open.inc' %] > <title >@@ -715,15 +716,16 @@ > <div id="field_marceditor[% subfield_loo.tag | html %][% subfield_loo.subfield | html %][% subfield_loo.random | html %]" class="field_marceditor"> > [% IF ( mv.type == 'select' ) %] > [% IF mv.category AND CAN_user_parameters_manage_auth_values %] >- <select name="[%- mv.name | html -%]" tabindex="1" class="input_marceditor" id="[%- mv.id | html -%]" data-category="[% mv.category | html %]"> >+ <select name="[%- mv.name | html -%]" tabindex="1" class="input_marceditor" id="[%- mv.id | html -%]" data-category="[% mv.category | html %]" data-parent-category="[% mv.authorised_value_category.parent_category_name | html %]"> > [% ELSE %] > <select name="[%- mv.name | html -%]" tabindex="1" class="input_marceditor select2" id="[%- mv.id | html -%]"> > [% END %] > [% FOREACH aval IN mv.values %] >+ [% attributes = HTML.attributes(mv.attributes.$aval) %] > [% IF aval == mv.default %] >- <option value="[%- aval | html -%]" selected="selected">[%- mv.labels.$aval | html -%]</option> >+ <option value="[%- aval | html -%]" [% attributes | $raw %] selected="selected">[%- mv.labels.$aval | html -%]</option> > [% ELSE %] >- <option value="[%- aval | html -%]">[%- mv.labels.$aval | html -%]</option> >+ <option value="[%- aval | html -%]" [% attributes | $raw %]>[%- mv.labels.$aval | html -%]</option> > [% END %] > [% END %] > </select> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt >index 0eb965b85b0..f24c0c15c36 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt >@@ -1,6 +1,7 @@ > [% USE raw %] > [% USE Asset %] > [% USE Koha %] >+[% USE HTML %] > [% USE HtmlTags %] > [% PROCESS 'i18n.inc' %] > [% INCLUDE 'doc-head-open.inc' %] >@@ -1181,22 +1182,23 @@ > <textarea cols="70" rows="4" id="[%- mv.id | html -%]" name="[%- mv.name | html -%]" class="input_marceditor" tabindex="1">[%- mv.value | html -%]</textarea> > [% ELSIF ( mv.type == 'select' ) %] > [% IF mv.category AND CAN_user_parameters_manage_auth_values %] >- <select name="[%- mv.name | html -%]" tabindex="1" class="input_marceditor" id="[%- mv.id | html -%]" data-category="[% mv.category | html %]"> >+ <select name="[%- mv.name | html -%]" tabindex="1" class="input_marceditor" id="[%- mv.id | html -%]" data-category="[% mv.category | html %]" data-parent-category="[% mv.authorised_value_category.parent_category_name | html %]"> > [% ELSE %] > <select name="[%- mv.name | html -%]" tabindex="1" class="input_marceditor select2" id="[%- mv.id | html -%]"> > [% END %] > [% SET matched = 0 %] > [% FOREACH aval IN mv.values %] >+ [% attributes = HTML.attributes(mv.attributes.$aval) %] > [% IF aval == mv.default %] > [% SET matched = 1 %] >- <option value="[%- aval | html -%]" selected="selected">[%- mv.labels.$aval | html -%]</option> >+ <option value="[%- aval | html -%]" selected="selected" [% attributes | $raw %]>[%- mv.labels.$aval | html -%]</option> > [% ELSE %] >- <option value="[%- aval | html -%]">[%- mv.labels.$aval | html -%]</option> >+ <option value="[%- aval | html -%]" [% attributes | $raw %]>[%- mv.labels.$aval | html -%]</option> > [% END %] > > [% END %] > [% UNLESS matched # If the current value is not in the authorised value list %] >- <option value="[%- mv.default | html -%]" selected="selected">[%- mv.default | html -%] (Not an authorised value)</option> >+ <option value="[%- mv.default | html -%]" selected="selected" [% attributes | $raw %]>[%- mv.default | html -%] (Not an authorised value)</option> > </select> > <span style="float:right;" title="The current value [% mv.default | html %] is not configured for the authorised value category controlling this subfield"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i></span> > [% ELSE %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/services/itemrecorddisplay.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/services/itemrecorddisplay.tt >index 412ba08f89d..d5b4dacbb3d 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/services/itemrecorddisplay.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/services/itemrecorddisplay.tt >@@ -1,4 +1,5 @@ > [% USE raw %] >+[% USE HTML %] > <ol> > [% FOREACH iteminfo IN iteminformation %] > <li style="[% iteminfo.hidden | html %];"> >@@ -9,12 +10,13 @@ > <label>[% iteminfo.subfield | html %] - [% iteminfo.marc_lib | $raw %]</label> > [% END %] > [% IF ( iteminfo.marc_value.type == 'select' ) %] >- <select name="field_value" class="input_marceditor"> >+ <select name="field_value" class="input_marceditor" data-category="[% iteminfo.marc_value.category | html %]" data-parent-category="[% iteminfo.marc_value.authorised_value_category.parent_category_name | html %]"> > [% FOREACH value IN iteminfo.marc_value.values %] >+ [% attributes = HTML.attributes(iteminfo.marc_value.attributes.$value) %] > [% IF ( value == iteminfo.marc_value.default ) %] >- <option value="[% value | html %]" selected="selected">[% iteminfo.marc_value.labels.$value | html %]</option> >+ <option value="[% value | html %]" [% attributes | $raw %] selected="selected">[% iteminfo.marc_value.labels.$value | html %]</option> > [% ELSE %] >- <option value="[% value | html %]">[% iteminfo.marc_value.labels.$value | html %]</option> >+ <option value="[% value | html %]" [% attributes | $raw %]>[% iteminfo.marc_value.labels.$value | html %]</option> > [% END %] > [% END %] > </select> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/cataloging.js b/koha-tmpl/intranet-tmpl/prog/js/cataloging.js >index f35750951a1..70f7be44360 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/cataloging.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/cataloging.js >@@ -853,4 +853,37 @@ $(document).ready(function () { > "input.input_marceditor.framework_plugin", > callPluginEventHandler > ); >+ >+ // Enable/Disable authorised values depending on parent-child relationships >+ $(".marc_editor").on( >+ "change", >+ "select.input_marceditor[data-category]", >+ function (e) { >+ const select = e.target; >+ const category = select.dataset.category; >+ const authorised_value_id = select.selectedOptions[0]?.dataset.id; >+ const ancestor = select.closest(".tag, .marc_editor"); >+ const childSelects = Array.from( >+ ancestor.querySelectorAll(`select[data-parent-category]`) >+ ).filter(s => s.dataset.parentCategory === category); >+ >+ for (const s of childSelects) { >+ for (const el of s.options) { >+ if ( >+ authorised_value_id && >+ el.dataset.parentAuthorisedValueId && >+ el.dataset.parentAuthorisedValueId !== >+ authorised_value_id >+ ) { >+ el.disabled = true; >+ el.selected = false; >+ } else { >+ el.disabled = false; >+ } >+ } >+ $(s).trigger("change"); >+ } >+ } >+ ); >+ $(".marc_editor select.input_marceditor[data-category]").trigger("change"); > }); >-- >2.39.5 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 34198
:
189006
|
189007
|
189068
|
189069
|
189182