diff --git a/C4/SIP/Sip.pm b/C4/SIP/Sip.pm index b4056b59d0..f8ea568383 100644 --- a/C4/SIP/Sip.pm +++ b/C4/SIP/Sip.pm @@ -97,6 +97,7 @@ sub add_field { ); $value = ''; } + $value =~ s/\r\n|\r|\n/ /g; # CR or LF terminates a sip message # Protect against them in sip text fields @@ -140,7 +141,7 @@ sub maybe_add { } return ( defined($value) && length($value) ) - ? add_field( $fid, $value ) + ? add_field( $fid, $value, $server ) : ''; } diff --git a/C4/SIP/Sip/MsgType.pm b/C4/SIP/Sip/MsgType.pm index 1b8f0f8ca8..7cd3c4606e 100644 --- a/C4/SIP/Sip/MsgType.pm +++ b/C4/SIP/Sip/MsgType.pm @@ -24,7 +24,8 @@ use Koha::Patrons; use Koha::Patron::Attributes; use Koha::Plugins; use Koha::Items; -use Koha::DateUtils qw( output_pref ); +use Koha::DateUtils qw( output_pref ); +use Koha::TemplateUtils qw( process_tt ); use UNIVERSAL::can; @@ -625,10 +626,10 @@ sub handle_checkout { $resp .= timestamp; # Now for the variable fields - $resp .= add_field( FID_INST_ID, $inst, $server ); - $resp .= add_field( FID_PATRON_ID, $patron_id, $server ); - $resp .= add_field( FID_ITEM_ID, $item_id, $server ); - $resp .= add_field( FID_TITLE_ID, $item->title_id, $server ); + $resp .= add_field( FID_INST_ID, $inst, $server ); + $resp .= add_field( FID_PATRON_ID, $patron_id, $server ); + $resp .= add_field( FID_ITEM_ID, $item_id, $server ); + $resp .= add_field( FID_TITLE_ID, _format_title( { item => $item, server => $server } ) ); if ( $item->due_date ) { my $due_date = $account->{format_due_date} @@ -663,7 +664,7 @@ sub handle_checkout { # If the item is valid, provide the title, otherwise # leave it blank - $resp .= add_field( FID_TITLE_ID, $item ? $item->title_id : '', $server ); + $resp .= add_field( FID_TITLE_ID, $item ? _format_title( { item => $item, server => $server } ) : "" ); # Due date is required. Since it didn't get checked out, # it's not due, so leave the date blank @@ -763,7 +764,7 @@ sub handle_checkin { if ($item) { $resp .= add_field( FID_PERM_LOCN, $item->permanent_location, $server ); - $resp .= maybe_add( FID_TITLE_ID, $item->title_id, $server ); + $resp .= maybe_add( FID_TITLE_ID, _format_title( { item => $item, server => $server } ) ); $resp .= $item->build_additional_item_fields_string($server); } else { $resp .= add_field( FID_PERM_LOCN, "", $server ); @@ -1343,8 +1344,8 @@ sub handle_item_information { $resp .= maybe_add( FID_SCREEN_MSG, "Item is damaged", $server ); } - $resp .= add_field( FID_ITEM_ID, $item->id, $server ); - $resp .= add_field( FID_TITLE_ID, $item->title_id, $server ); + $resp .= add_field( FID_ITEM_ID, $item->id, $server ); + $resp .= add_field( FID_TITLE_ID, _format_title( { item => $item, server => $server } ) ); $resp .= maybe_add( FID_MEDIA_TYPE, $item->sip_media_type, $server ); $resp .= maybe_add( FID_PERM_LOCN, $item->permanent_location, $server ); @@ -1429,8 +1430,8 @@ sub handle_item_status_update { $resp .= $status->ok ? '1' : '0'; $resp .= timestamp; - $resp .= add_field( FID_ITEM_ID, $item->id, $server ); - $resp .= add_field( FID_TITLE_ID, $item->title_id, $server ); + $resp .= add_field( FID_ITEM_ID, $item->id, $server ); + $resp .= add_field( FID_TITLE_ID, _format_title( { item => $item, server => $server } ) ); $resp .= maybe_add( FID_ITEM_PROPS, $item->sip_item_properties, $server ); } @@ -1551,10 +1552,10 @@ sub handle_hold { ( $status->expiration_date ) and $resp .= maybe_add( FID_EXPIRATION, timestamp( $status->expiration_date ), $server ); - $resp .= maybe_add( FID_QUEUE_POS, $status->queue_position, $server ); - $resp .= maybe_add( FID_PICKUP_LOCN, $status->pickup_location, $server ); - $resp .= maybe_add( FID_ITEM_ID, $status->item->id, $server ); - $resp .= maybe_add( FID_TITLE_ID, $status->item->title_id, $server ); + $resp .= maybe_add( FID_QUEUE_POS, $status->queue_position, $server ); + $resp .= maybe_add( FID_PICKUP_LOCN, $status->pickup_location, $server ); + $resp .= maybe_add( FID_ITEM_ID, $status->item->id, $server ); + $resp .= maybe_add( FID_TITLE_ID, _format_title( { item => $status->item, server => $server } ), $server ); } else { # Not ok. still need required fields @@ -1618,9 +1619,9 @@ sub handle_renew { } $resp .= sipbool( desensitize( { status => $status, patron => $patron, server => $server } ) ); $resp .= timestamp; - $resp .= add_field( FID_PATRON_ID, $patron->id, $server ); - $resp .= add_field( FID_ITEM_ID, $item->id, $server ); - $resp .= add_field( FID_TITLE_ID, $item->title_id, $server ); + $resp .= add_field( FID_PATRON_ID, $patron->id, $server ); + $resp .= add_field( FID_ITEM_ID, $item->id, $server ); + $resp .= add_field( FID_TITLE_ID, _format_title( { item => $item, server => $server } ) ); if ( $item->due_date ) { $resp .= add_field( FID_DUE_DATE, timestamp( $item->due_date ), $server ); } else { @@ -1641,10 +1642,13 @@ sub handle_renew { # If we found the patron or the item, the return the ILS # information, otherwise echo back the information we received # from the terminal - $resp .= add_field( FID_PATRON_ID, $patron ? $patron->id : $patron_id, $server ); - $resp .= add_field( FID_ITEM_ID, $item ? $item->id : $item_id, $server ); - $resp .= add_field( FID_TITLE_ID, $item ? $item->title_id : $title_id, $server ); - $resp .= add_field( FID_DUE_DATE, '', $server ); + $resp .= add_field( FID_PATRON_ID, $patron ? $patron->id : $patron_id, $server ); + $resp .= add_field( FID_ITEM_ID, $item ? $item->id : $item_id, $server ); + $resp .= add_field( + FID_TITLE_ID, $item ? _format_title( { item => $item, server => $server } ) : $title_id, + $server + ); + $resp .= add_field( FID_DUE_DATE, '', $server ); } if ( $status->fee_amount ) { @@ -1900,6 +1904,19 @@ sub desensitize { return 1; } +sub _format_title { + my ($params) = @_; + my $server = $params->{server}; + my $item = $params->{item}; + if ( defined $server->{account}->{aj_field_template} && $server->{account}->{aj_field_template} ne '' ) { + return process_tt( + $server->{account}->{aj_field_template}, + { biblio => $item->{object}->biblio, item => $item->{object} } + ); + } else { + return $item->title_id; + } +} 1; __END__ diff --git a/api/v1/swagger/definitions/sip2_account.yaml b/api/v1/swagger/definitions/sip2_account.yaml index 8d18dc7618..f7d6fc6cb1 100644 --- a/api/v1/swagger/definitions/sip2_account.yaml +++ b/api/v1/swagger/definitions/sip2_account.yaml @@ -13,6 +13,11 @@ properties: type: - string - 'null' + aj_field_template: + description: aj field template + type: + - string + - 'null' allow_additional_materials_checkout: description: allow additional materials checkout type: diff --git a/installer/data/mysql/atomicupdate/bug_39829.pl b/installer/data/mysql/atomicupdate/bug_39829.pl new file mode 100755 index 0000000000..f6209ea102 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_39829.pl @@ -0,0 +1,25 @@ +use Modern::Perl; +use Koha::Installer::Output qw(say_warning say_success say_info); + +return { + bug_number => "39829", + description => "Add SIP aj_field_template to allow customizing title info", + up => sub { + my ($args) = @_; + my ( $dbh, $out ) = @$args{qw(dbh out)}; + + unless ( column_exists( 'sip_accounts', 'aj_field_template' ) ) { + $dbh->do( + q{ + ALTER TABLE sip_accounts + ADD COLUMN + `aj_field_template` text NULL + AFTER `ae_field_template` + } + ); + say $out "Added column 'sip_accounts.aj_field_template'"; + } else { + say $out "Column already existed: 'sip_accounts.aj_field_template'"; + } + }, +}; diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index 422be2a470..649b8d30a5 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -6333,6 +6333,7 @@ CREATE TABLE `sip_accounts` ( `sip_account_id` int(11) NOT NULL AUTO_INCREMENT, `sip_institution_id` int(11) NOT NULL COMMENT 'Foreign key to sip_institutions.sip_institution_id', `ae_field_template` text DEFAULT NULL, + `aj_field_template` text DEFAULT NULL, `allow_additional_materials_checkout` tinyint(1) DEFAULT NULL, `allow_empty_passwords` tinyint(1) DEFAULT NULL, `allow_fields` varchar(255) DEFAULT NULL, diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/SIP2/SIP2AccountResource.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/SIP2/SIP2AccountResource.vue index 457bf595e4..73a6bd2ce8 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/SIP2/SIP2AccountResource.vue +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/SIP2/SIP2AccountResource.vue @@ -957,6 +957,19 @@ export default { "(Hold patron name) is the name of the patron who has placed a hold on the item being checked in. Use Template Toolkit syntax - the patron fields (Koha::Patron) for the hold patron and item fields are available." ), }, + { + name: "aj_field_template", + type: "textarea", + label: "AJ " + __("field template"), + placeholder: "[% biblio.title %]", + hideIn: ["List"], + group: "templates", + toolTip: + "AJ " + + __( + "(Title) is the item's title as sent to the SIP2 client. Use Template Toolkit syntax - the item and biblio fields (Koha::Item, Koha::Biblio) are available." + ), + }, ]; const baseResource = useBaseResource({ diff --git a/t/cypress/integration/SIP2/Accounts.ts b/t/cypress/integration/SIP2/Accounts.ts index 85391c8d7b..e7787d42c3 100644 --- a/t/cypress/integration/SIP2/Accounts.ts +++ b/t/cypress/integration/SIP2/Accounts.ts @@ -3,6 +3,7 @@ import { mount } from "@cypress/vue"; function get_account() { return { ae_field_template: "", + aj_field_template: "", allow_additional_materials_checkout: false, allow_empty_passwords: false, allow_fields: null, diff --git a/t/db_dependent/Koha/SIP2/Accounts.t b/t/db_dependent/Koha/SIP2/Accounts.t index c35e90c988..e9dd81436a 100755 --- a/t/db_dependent/Koha/SIP2/Accounts.t +++ b/t/db_dependent/Koha/SIP2/Accounts.t @@ -260,6 +260,7 @@ subtest 'template fields accept long text' => sub { class => 'Koha::SIP2::Accounts', value => { ae_field_template => $template, + aj_field_template => $template, av_field_template => $template, da_field_template => $template, }, @@ -268,6 +269,7 @@ subtest 'template fields accept long text' => sub { my $stored = $account->get_from_storage; is( $stored->ae_field_template, $template, 'ae_field_template can store large templates' ); + is( $stored->aj_field_template, $template, 'aj_field_template can store large templates' ); is( $stored->av_field_template, $template, 'av_field_template can store large templates' ); is( $stored->da_field_template, $template, 'da_field_template can store large templates' ); diff --git a/t/db_dependent/SIP/Message.t b/t/db_dependent/SIP/Message.t index f0db09403f..1083068a24 100755 --- a/t/db_dependent/SIP/Message.t +++ b/t/db_dependent/SIP/Message.t @@ -22,7 +22,7 @@ use Modern::Perl; use Test::NoWarnings; -use Test::More tests => 23; +use Test::More tests => 24; use Test::Exception; use Test::MockObject; use Test::MockModule; @@ -2009,6 +2009,61 @@ subtest 'Test convert_nonprinting_characters' => sub { $schema->storage->txn_rollback; }; +subtest 'Test _format_title' => sub { + + plan tests => 2; + my $schema = Koha::Database->new->schema; + $schema->storage->txn_begin; + my $builder = t::lib::TestBuilder->new(); + my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode}; + my ( $response, $findpatron ); + my $mocks = create_mocks( \$response, \$findpatron, \$branchcode ); + my $mockILS = $mocks->{ils}; + my $server = { ils => $mockILS, account => {} }; + + my $patron = $builder->build( + { + source => 'Borrower', + value => { + password => hash_password(PATRON_PW), + branchcode => $branchcode, + }, + } + ); + my $biblio = $builder->build_sample_biblio( + { + title => 'Title', + } + ); + $biblio->subtitle("Subtitle")->store; + my $item = $builder->build_sample_item( + { + damaged => 0, + withdrawn => 0, + itemlost => 0, + restricted => 0, + homebranch => $branchcode, + holdingbranch => $branchcode, + biblionumber => $biblio->biblionumber + } + ); + my $sip_item = C4::SIP::ILS::Item->new( $item->barcode ); + + delete $server->{account}->{aj_field_template}; + is( + C4::SIP::Sip::MsgType::_format_title( { item => $sip_item, server => $server } ), "Title", + "When aj_field_template undefined, we get the simple title" + ); + + $server->{account}->{aj_field_template} = "[% biblio.title %] [% biblio.subtitle %] [% item.barcode %]"; + is( + C4::SIP::Sip::MsgType::_format_title( { item => $sip_item, server => $server } ), + "Title Subtitle " . $item->barcode, "When aj_field_template set, we get expected fields" + ); + + $schema->storage->txn_rollback; +}; + # Helper routines sub create_mocks {