Bug 40192: Add additional fields support to ERM titles

This patch extends the additional fields framework to ERM e-holdings
titles, allowing libraries to configure custom metadata fields for
managing electronic resources.

Implementation details:
- Extended Koha::ERM::EHoldings::Title and ::Titles with
  AdditionalFields mixin for object-level support
- Added 'erm_titles' table mapping to AdditionalField.pm for API
  serialization
- Updated OpenAPI schema definitions to include extended_attributes
  in erm_eholdings_title.yaml and expose extended_attribute_types
  endpoint
- Enhanced REST API controller to handle additional fields in title
  creation/updates
- Updated admin interface to allow configuring additional fields for
  the 'title' table name
- Modified Vue.js EHoldingsLocalTitleResource component to display
  and manage custom fields

This enables institutions to track custom metadata (e.g., license
details, administrative notes, local identifiers) alongside standard
ERM title information.

Test plan:
1. Configure additional fields:
   - Go to Administration > Additional fields
   - Click "New field" and select table name "title"
   - Create a text field: Name="License notes", Searchable=Yes
   - Create an authorized value field: Name="Access level",
     AV category=LOC (or any available category)
   - Save both fields

2. Test with local ERM titles:
   - Navigate to E-resource management > Local titles
   - Create a new local title or edit an existing one
   - Verify the "Additional fields" section appears with your
     configured fields
   - Fill in "License notes" with test text
   - Select a value for "Access level"
   - Save the title

3. Verify persistence:
   - Reopen the title you just edited
   - Confirm both additional field values are displayed correctly
   - Edit one field value and save
   - Confirm the update persists

Signed-off-by: Michaela <michaela.sieber@kit.edu>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Matt Blenkinsop 2025-10-21 10:56:32 +01:00 committed by Lucas Gass
parent c04ac1c541
commit 806ac743f0
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
12 changed files with 67 additions and 5 deletions

View File

@ -64,6 +64,7 @@ sub to_api {
'erm_packages' => 'package',
'aqorders' => 'order',
'aqbooksellers:vendor' => 'vendor',
'erm_titles' => 'title'
};
my $json = $self->SUPER::to_api($params);

View File

@ -22,7 +22,7 @@ use MIME::Base64 qw( decode_base64 );
use Koha::Database;
use base qw(Koha::Object);
use base qw(Koha::Object::Mixin::AdditionalFields Koha::Object);
use C4::Biblio qw( AddBiblio TransformKohaToMarc GetMarcFromKohaField );

View File

@ -21,7 +21,7 @@ use Koha::Database;
use Koha::ERM::EHoldings::Title;
use base qw(Koha::Objects);
use base qw(Koha::Objects::Mixin::AdditionalFields Koha::Objects);
=head1 NAME

View File

@ -90,12 +90,17 @@ sub add {
my $resources = delete $body->{resources} // [];
my $create_linked_biblio = delete $body->{create_linked_biblio} // 0;
my $extended_attributes = delete $body->{extended_attributes} // [];
my $title = Koha::ERM::EHoldings::Title->new_from_api($body)
->store( { create_linked_biblio => $create_linked_biblio } );
$title->resources($resources);
my @extended_attributes =
map { { 'id' => $_->{field_id}, 'value' => $_->{value} } } @{$extended_attributes};
$title->extended_attributes( \@extended_attributes );
$c->res->headers->location( $c->req->url->to_string . '/' . $title->title_id );
return $c->render(
status => 201,
@ -152,10 +157,14 @@ sub update {
my $resources = delete $body->{resources} // [];
my $create_linked_biblio = delete $body->{create_linked_biblio} // 0;
my $extended_attributes = delete $body->{extended_attributes} // [];
$title->set_from_api($body)->store( { create_linked_biblio => $create_linked_biblio } );
$title->resources($resources);
my @extended_attributes =
map { { 'id' => $_->{field_id}, 'value' => $_->{value} } } @{$extended_attributes};
$title->extended_attributes( \@extended_attributes );
return $c->render(
status => 200,

View File

@ -53,6 +53,7 @@ sub _list {
package => 'erm_packages',
order => 'aqorders',
vendor => 'aqbooksellers:vendor',
title => 'erm_titles'
};
my @tables;

View File

@ -297,6 +297,38 @@ __PACKAGE__->has_many(
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2023-09-13 11:06:45
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SOFpN45kBTi/qvW5OBnYDA
__PACKAGE__->has_many(
"additional_field_values",
"Koha::Schema::Result::AdditionalFieldValue",
sub {
my ($args) = @_;
return {
"$args->{foreign_alias}.record_id" => { -ident => "$args->{self_alias}.title_id" },
"$args->{foreign_alias}.field_id" =>
{ -in => \'(SELECT id FROM additional_fields WHERE tablename="erm_titles")' },
};
},
{ cascade_copy => 0, cascade_delete => 0 },
);
__PACKAGE__->has_many(
"extended_attributes",
"Koha::Schema::Result::AdditionalFieldValue",
sub {
my ($args) = @_;
return {
"$args->{foreign_alias}.record_id" => { -ident => "$args->{self_alias}.title_id" },
"$args->{foreign_alias}.field_id" =>
{ -in => \'(SELECT id FROM additional_fields WHERE tablename="erm_titles")' },
};
},
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 koha_objects_class
Missing POD for koha_objects_class.

View File

@ -173,6 +173,16 @@ properties:
type:
- boolean
- "null"
extended_attributes:
description: Related additional field values
type:
- array
items:
$ref: extended_attribute_value.yaml
_strings:
type:
- object
- "null"
additionalProperties: false
required:

View File

@ -153,6 +153,8 @@
type: string
enum:
- resources.package
- extended_attributes
- +strings
collectionFormat: csv
- $ref: "../swagger.yaml#/parameters/match"
- $ref: "../swagger.yaml#/parameters/order_by"
@ -278,6 +280,8 @@
enum:
- resources
- resources.package
- extended_attributes
- +strings
collectionFormat: csv
responses:
200:

View File

@ -24,6 +24,7 @@
- order
- subscription
- vendor
- title
- $ref: "../swagger.yaml#/parameters/match"
- $ref: "../swagger.yaml#/parameters/order_by"
- $ref: "../swagger.yaml#/parameters/page"

View File

@ -30,7 +30,7 @@
</head>
[% marcfield_tables = ['subscription', 'aqorders'] %]
[% searchable_tables = ['subscription', 'aqbasket', 'aqinvoices', 'erm_licenses', 'erm_agreements', 'erm_packages', 'aqbooksellers:vendor'] %]
[% searchable_tables = ['subscription', 'aqbasket', 'aqinvoices', 'erm_licenses', 'erm_agreements', 'erm_packages', 'aqbooksellers:vendor', 'erm_titles'] %]
[% show_marcfield = marcfield_tables.grep('^' _ tablename _ '$').size ? 1 : 0 %]
[% show_searchable = searchable_tables.grep('^' _ tablename _ '$').size ? 1 : 0 %]
@ -146,6 +146,7 @@
[% WRAPPER table_option value="erm_agreements" %]Agreements[% END %]
[% WRAPPER table_option value="erm_licenses" %]Licenses[% END %]
[% WRAPPER table_option value="erm_packages" %]Packages[% END %]
[% WRAPPER table_option value="erm_titles" %]Titles[% END %]
</ul>
[% END %]
[% IF CAN_user_updatecharges_remaining_permissions || CAN_user_parameters_manage_accounts %]

View File

@ -155,7 +155,8 @@ export class ERMAPIClient {
this.httpClient.get({
endpoint: "eholdings/local/titles/" + id,
headers: {
"x-koha-embed": "resources,resources.package",
"x-koha-embed":
"resources,resources.package,extended_attributes,+strings",
},
}),
delete: id =>

View File

@ -68,6 +68,7 @@ export default {
props,
additionalToolbarButtons,
moduleStore: "ERMStore",
extendedAttributesResourceType: "title",
resourceAttrs: [
{
name: "publication_title",
@ -443,7 +444,7 @@ export default {
const tableOptions = {
url: baseResource.getResourceTableUrl(),
options: {
embed: "resources.package",
embed: "resources.package,extended_attributes,+strings",
searchCols: [
{ search: defaults.publication_title },
null,
@ -491,6 +492,7 @@ export default {
let title_id = title.title_id;
delete title.title_id;
delete title.biblio_id;
delete title._strings;
// Cannot use the map/keepAttrs because of the reserved keyword 'package'
title.resources.forEach(function (e) {