From 284eb23028730cc6bdda87120dba513f6ce7ec35 Mon Sep 17 00:00:00 2001 From: Julian Maurice 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 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 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..3811b7d5551 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 || undef, + 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 || 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 %]
    [% 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 %] - + [% ELSE %] [% IF subfield.IS_LOST_AV && Koha.Preference("ClaimReturnedLostValue") && aval == Koha.Preference("ClaimReturnedLostValue") %] - + [% ELSIF subfield.IS_LOST_AV && Koha.Preference("BundleLostValue") && aval == Koha.Preference("BundleLostValue") %] - + [% ELSE %] - + [% 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 #%] - + [% END %] [% 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' %] 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