From b77511794ad43f8006350c7e0c72cca76ff23e0c Mon Sep 17 00:00:00 2001 From: Nick Clemens Date: Mon, 3 Jan 2022 15:24:01 +0000 Subject: [PATCH] Bug 28633: Add preferred name field to patrons This patch adds a new field 'preferred_name' to the patron record. On storage (creation or update) the preferred_name is set to the firstname if no value is passed. Patron modifications will set the preferred name to the firstname if the preferred_name field is hidden With this patchset preferred_name will always be set - either to the firstname, or a specified value. PatronAutoComplete/ysearch is updated to use 'preferred_name' To test: 1 - Apply patches 2 - Update database and restart all, clear browser cache 3 - Load a patron in staff module 4 - Confirm you see and can add a preferred name 5 - Confirm the preferred name and first name now displays on patron details 6 - Remove first name from patron record and confirm it no longer shows 7 - Edit sysprefs BorrowerMandatoryFields and BorrowerUnwantedFields to confirm you can make new field required or hidden 8 - Sign in as patron to opac 9 - Confirm preferred name shows 10 - Edit account on opac and confirm field is present 11 - Verify DefaultPatronSearchFields contains 'preferredname' if your pref had firstname 12 - Perform checkout and patron search using preferred_name, confirm patron is found 13 - Enable PatronAutoComplete system preference 14 - Type patron's surname into Checkout or patron search but don't hit enter 15 - Confirm patron is displayed with 'preferred_name' in the preview 16 - Set 'preferred_name' in all 'Unwanted' preferences 17 - Confirm editing a patron in staff interface sets both fields when firstname updated 18 - Confirm a patron modification sets both fields when firstname updated 19 - Create a patron / perform self registration and confirm both fields set when preferred_name is hidden 20 - Remove preferred_name from Unwanted prefs and confirm preferred_name is set to firstname if nothing passed --- Koha/Database/Columns.pm | 1 + Koha/Patron.pm | 5 ++ Koha/Patron/Modification.pm | 3 + api/v1/swagger/paths/patrons.yaml | 5 ++ .../prog/en/includes/js-patron-format.inc | 5 +- .../prog/en/includes/patron-search.inc | 4 +- .../prog/en/includes/patron-title.inc | 7 ++- .../prog/en/includes/patronfields.inc | 3 +- .../en/modules/admin/preferences/patrons.pref | 2 +- .../prog/en/modules/members/memberentrygen.tt | 17 +++++- .../prog/en/modules/members/members-update.tt | 1 + .../prog/en/modules/members/moremember.tt | 10 +++- .../intranet-tmpl/prog/js/staff-global.js | 2 +- .../bootstrap/en/includes/patron-title.inc | 2 +- .../bootstrap/en/modules/opac-memberentry.tt | 15 ++++- members/memberentry.pl | 3 + opac/opac-memberentry.pl | 3 + t/db_dependent/Koha/Patron.t | 60 ++++++++++++++++++- t/db_dependent/api/v1/patrons.t | 5 +- 19 files changed, 134 insertions(+), 19 deletions(-) diff --git a/Koha/Database/Columns.pm b/Koha/Database/Columns.pm index d6cd1d407fa..985460895bd 100644 --- a/Koha/Database/Columns.pm +++ b/Koha/Database/Columns.pm @@ -174,6 +174,7 @@ sub columns { "password" => __("Password"), "phone" => __("Primary phone"), "phonepro" => __("Secondary phone"), + "preferred_name" => __("Preferred_name"), "primary_contact_method" => __("Primary contact method"), "privacy_guarantor_checkouts" => __("Show checkouts to guarantor"), "privacy_guarantor_fines" => __("Show fines to guarantor"), diff --git a/Koha/Patron.pm b/Koha/Patron.pm index 59bf29d7301..2939b96ddd8 100644 --- a/Koha/Patron.pm +++ b/Koha/Patron.pm @@ -228,6 +228,11 @@ sub store { $self->surname( uc( $self->surname ) ) if C4::Context->preference("uppercasesurnames"); + # Add preferred name unless specified + unless ( $self->preferred_name ) { + $self->preferred_name( $self->firstname ); + } + $self->relationship(undef) # We do not want to store an empty string in this field if defined $self->relationship and $self->relationship eq ""; diff --git a/Koha/Patron/Modification.pm b/Koha/Patron/Modification.pm index 1b42223f048..d26aa0b3b23 100644 --- a/Koha/Patron/Modification.pm +++ b/Koha/Patron/Modification.pm @@ -103,6 +103,9 @@ sub approve { delete $data->{$key}; } + my $unwanted = C4::Context->preference('PatronSelfModificationBorrowerUnwantedField'); + $data->{preferred_name} = $data->{firstname} if ( $unwanted =~ 'preferred_name' && defined $data->{firstname} ); + $patron->set($data); # Take care of extended attributes diff --git a/api/v1/swagger/paths/patrons.yaml b/api/v1/swagger/paths/patrons.yaml index cf05d748d8b..3cdc3224457 100644 --- a/api/v1/swagger/paths/patrons.yaml +++ b/api/v1/swagger/paths/patrons.yaml @@ -29,6 +29,11 @@ description: Case insensitive search on firstname required: false type: string + - name: preferred_name + in: query + description: Case insensitive search on preferred name + required: false + type: string - name: title in: query description: Case insensitive search on title diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/js-patron-format.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/js-patron-format.inc index 69b472d97be..fd8463ba3a0 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/js-patron-format.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/js-patron-format.inc @@ -20,6 +20,7 @@ var name; var firstname = escape_str(patron.firstname); + var preferred_name = escape_str(patron.preferred_name); var surname = escape_str(patron.surname); if ( patron.middle_name != null && patron.middle_name != '' ) { @@ -30,10 +31,10 @@ firstname += ' (' + escape_str(patron.other_name) + ')'; } if ( config && config.invert_name ) { - name = surname + ( firstname ? ', ' + firstname : '' ); + name = surname + ( preferred_name ? ', ' + preferred_name : '' ); } else { - name = firstname + ' ' + surname; + name = preferred_name + ' ' + surname; } if ( name.replace(' ', '').length == 0 ) { diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc index d2eef529c7c..dd53cc2fa43 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc @@ -536,7 +536,7 @@ } [% CASE 'name-address' %] { - "data": "me.surname:me.firstname:me.middle_name:me.othernames:me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country", + "data": "me.surname:me.preferred_name:me.firstname:me.middle_name:me.othernames:me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country", "searchable": true, "orderable": true, "render": function( data, type, row, meta ) { @@ -561,7 +561,7 @@ } [% CASE 'name' %] { - "data": "me.surname:me.firstname:me.middle_name:me.othernames", + "data": "me.surname:me.preferred_name:me.firstname:me.middle_name:me.othernames", "searchable": true, "orderable": true, "render": function( data, type, row, meta ) { diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-title.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-title.inc index a633697a595..72d4472a833 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-title.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-title.inc @@ -7,6 +7,7 @@ [%- SET data.surname = patron.surname -%] [%- SET data.othernames = patron.othernames -%] [%- SET data.firstname = patron.firstname -%] + [%- SET data.preferred_name = patron.preferred_name -%] [%- SET data.middle_name = patron.middle_name -%] [%- SET data.cardnumber = patron.cardnumber -%] [%- SET data.borrowernumber = patron.borrowernumber -%] @@ -16,6 +17,7 @@ [%- SET data.surname = borrower.surname -%] [%- SET data.othernames = borrower.othernames -%] [%- SET data.firstname = borrower.firstname -%] + [%- SET data.preferred_name = borrower.preferred_name -%] [%- SET data.middle_name = borrower.middle_name -%] [%- SET data.cardnumber = borrower.cardnumber -%] [%- SET data.borrowernumber = borrower.borrowernumber -%] @@ -25,6 +27,7 @@ [%- SET data.surname = surname -%] [%- SET data.othernames = othernames -%] [%- SET data.firstname = firstname -%] + [%- SET data.preferred_name = preferred_name -%] [%- SET data.middle_name = middle_name -%] [%- SET data.cardnumber = cardnumber -%] [%- SET data.borrowernumber = borrowernumber -%] @@ -66,9 +69,9 @@ [%- IF data.category_type == 'I' -%] [%- data.surname | html -%] [%- IF data.othernames -%] ([%- data.othernames | html -%])[%- END -%] [%- ELSIF invert_name -%] - [%- data.title | $raw -%][%- data.surname | html -%][%- IF ( data.firstname ) -%], [% data.firstname | html -%][%- END -%][%- IF data.middle_name -%] [% data.middle_name | html -%][%- END -%][%- IF data.othernames -%] ([%- data.othernames | html -%])[%- END -%] + [%- data.title | $raw -%][%- data.surname | html -%][%- IF ( data.preferred_name ) -%], [% data.preferred_name | html -%][%- END -%][%- IF data.middle_name -%] [% data.middle_name | html -%][%- END -%][%- IF data.othernames -%] ([%- data.othernames | html -%])[%- END -%] [%- ELSE -%] - [%- data.title | $raw -%][%- data.firstname | html %][%- IF data.middle_name -%] [% data.middle_name | html -%][%- END -%][%- IF data.othernames -%] ([%- data.othernames | html -%]) [%- END -%] [% data.surname | html -%] + [%- data.title | $raw -%][%- data.preferred_name | html %][%- IF data.middle_name -%] [% data.middle_name | html -%][%- END -%][%- IF data.othernames -%] ([%- data.othernames | html -%]) [%- END -%] [% data.surname | html -%] [%- END -%] [%- IF display_cardnumber AND data.cardnumber -%] ([%- data.cardnumber | html -%])[%- END -%] [%- ELSIF display_cardnumber -%] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patronfields.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patronfields.inc index 9e0477f4a91..d230bbf4c0f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/patronfields.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patronfields.inc @@ -8,6 +8,7 @@ [%- CASE 'cardnumber' -%][% t("Card number") | html %] [%- CASE 'surname' -%][% t("Surname") | html %] [%- CASE 'firstname' -%][% t("First name") | html %] + [%- CASE 'preferred_name' -%][% t("Preferred name") | html %] [%- CASE 'middle_name' -%][% t("Middle name") | html %] [%- CASE 'title' -%][% t("Salutation") | html %] [%- CASE 'othernames' -%][% t("Other name") | html %] @@ -86,7 +87,7 @@ + [% IF ( mandatorypreferred_name ) %] + Required + [% END %] + + [% END #/UNLESS nopreferred_name %] [% UNLESS nomiddle_name %]
  • [% IF ( mandatorymiddle_name ) %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt index 850db09f072..9ae3cf2c596 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/members-update.tt @@ -25,6 +25,7 @@ [% CASE 'branchcode' %]Home library (branchcode) [% CASE 'surname' %]Surname [% CASE 'firstname' %]First name +[% CASE 'preferred_name' %]Preferred name [% CASE 'middle_name' %]Middle name [% CASE 'title' %]Title [% CASE 'othernames' %]Other names diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt index 3360a04a6a7..43760ac154f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt @@ -135,6 +135,12 @@
      + [% IF ( patron.preferred_name && patron.firstname ) %] +
    1. + First name: + [% patron.firstname | html %] +
    2. + [% END %] [% IF ( patron.phone ) %]
    3. Primary phone: @@ -225,7 +231,7 @@ [% IF logged_in_user.can_see_patron_infos( guarantee ) %]
    4. [% guarantee.firstname | html %] [% guarantee.surname | html %]
    5. [% ELSE %] -
    6. [% guarantee.firstname | html %] [% guarantee.surname | html %]
    7. +
    8. [% guarantee.preferred_name | html %] [% guarantee.surname | html %]
    9. [% END %] [% END %] @@ -241,7 +247,7 @@ [% FOREACH gr IN guarantor_relationships %] [% SET guarantor = gr.guarantor %] [% IF logged_in_user.can_see_patron_infos( guarantor ) %] -
    10. [% guarantor.firstname | html %] [% guarantor.surname | html %][% IF gr.relationship %] ([% gr.relationship | html %])[% END %]
    11. +
    12. [% guarantor.preferred_name | html %] [% guarantor.surname | html %][% IF gr.relationship %] ([% gr.relationship | html %])[% END %]
    13. [% END %] [% END %] [% IF patron.contactfirstname OR patron.contactname %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/staff-global.js b/koha-tmpl/intranet-tmpl/prog/js/staff-global.js index b05936f48f9..e993f8fe011 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/staff-global.js +++ b/koha-tmpl/intranet-tmpl/prog/js/staff-global.js @@ -709,7 +709,7 @@ function patron_autocomplete(node, options) { (item.link ? '' : "") + (item.surname ? item.surname.escapeHtml() : "") + ", " + - (item.firstname ? item.firstname.escapeHtml() : "") + + (item.preferred_name ? item.preferred_name.escapeHtml() : item.firstname ? item.firstname.escapeHtml() : "") + " " + (item.middle_name ? item.middle_name.escapeHtml() : "") + " " + diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/patron-title.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/patron-title.inc index d34475359df..da530cd6450 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/patron-title.inc +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/patron-title.inc @@ -4,5 +4,5 @@ [%- IF patron.title -%] [% patron.title | html %] [%- END -%] - [% patron.firstname | html %] [% patron.middle_name | html %] [% patron.surname | html %] + [% patron.preferred_name | html %] [% patron.middle_name | html %] [% patron.surname | html %] [%- END -%] diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt index f96165ddde6..6db0556cfdf 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt @@ -194,7 +194,7 @@ Guaranteed by [% FOREACH gr IN patron.guarantor_relationships %] [% SET g = gr.guarantor %] - [% g.firstname | html %] [% g.middle_name | html %] [% g.surname | html %] + [% g.preferred_name | html %] [% g.middle_name | html %] [% g.surname | html %] [%- IF ! loop.last %], [% END %] [% END %] @@ -212,7 +212,7 @@
      [% INCLUDE 'csrf-token.inc' %] - [% FOREACH field = ['streetnumber' 'streettype' 'cardnumber' 'branchcode' 'categorycode' 'title' 'surname' 'firstname' 'middle_name' 'dateofbirth' 'initials' 'pronouns' 'othernames' 'address' 'address2' 'city' 'state' 'zipcode' 'country' 'phone' 'phonepro' 'mobile' 'email' 'emailpro' 'fax' 'B_streettype' 'B_streetnumber' 'B_address' 'B_address2' 'B_city' 'B_state' 'B_zipcode' 'B_country' 'B_phone' 'B_email' 'contactnote' 'altcontactsurname' 'altcontactfirstname' 'altcontactaddress1' 'altcontactaddress2' 'altcontactaddress3' 'altcontactstate' 'altcontactzipcode' 'altcontactcountry' 'altcontactphone' 'password' 'lang' ] %] + [% FOREACH field = ['streetnumber' 'streettype' 'cardnumber' 'branchcode' 'categorycode' 'title' 'surname' 'firstname' 'preferred_name' 'middle_name' 'dateofbirth' 'initials' 'pronouns' 'othernames' 'address' 'address2' 'city' 'state' 'zipcode' 'country' 'phone' 'phonepro' 'mobile' 'email' 'emailpro' 'fax' 'B_streettype' 'B_streetnumber' 'B_address' 'B_address2' 'B_city' 'B_state' 'B_zipcode' 'B_country' 'B_phone' 'B_email' 'contactnote' 'altcontactsurname' 'altcontactfirstname' 'altcontactaddress1' 'altcontactaddress2' 'altcontactaddress3' 'altcontactstate' 'altcontactzipcode' 'altcontactcountry' 'altcontactphone' 'password' 'lang' ] %] [% IF mandatory.defined( field ) %] [% SET required.$field = 'required' %] [% END %] @@ -328,7 +328,7 @@ [% END # / defined 'branchcode' %] [%# Following on one line for translatability %] - [% UNLESS hidden.defined('title') && hidden.defined('surname') && hidden.defined('firstname') && hidden.defined('middle_name') && hidden.defined('dateofbirth') && hidden.defined('initials') && hidden.defined('pronouns') && hidden.defined('othernames') && hidden.defined('sex') %] + [% UNLESS hidden.defined('title') && hidden.defined('surname') && hidden.defined('firstname') && hidden.defined('preferred_name') && hidden.defined('middle_name') && hidden.defined('dateofbirth') && hidden.defined('initials') && hidden.defined('pronouns') && hidden.defined('othernames') && hidden.defined('sex') %]
      @@ -380,6 +380,15 @@ [% END %] + [% UNLESS hidden.defined('preferred_name') %] +
    14. + + + +
      Required
      +
    15. + [% END %] + [% UNLESS hidden.defined('dateofbirth') %]
    16. diff --git a/members/memberentry.pl b/members/memberentry.pl index 284bbd82d0c..f1fd9bfe5bd 100755 --- a/members/memberentry.pl +++ b/members/memberentry.pl @@ -525,6 +525,9 @@ if ((!$nok) and $nodouble and ($op eq 'cud-insert' or $op eq 'cud-save')){ $patron = Koha::Patrons->find( $borrowernumber ); + # Ensure preferred name is set even if not passed because of BorrowerUnwantedFields + $newdata{preferred_name} = undef unless defined $newdata{preferred_name}; + if ($NoUpdateEmail) { delete $newdata{'email'}; delete $newdata{'emailpro'}; diff --git a/opac/opac-memberentry.pl b/opac/opac-memberentry.pl index b671dde7c04..399d31afa4b 100755 --- a/opac/opac-memberentry.pl +++ b/opac/opac-memberentry.pl @@ -330,6 +330,9 @@ elsif ( $op eq 'cud-update' ) { $template->param( op => 'edit' ); } else { + # If preferred name is not included but firstname is then set preferred_name to firstname + $borrower{preferred_name} = $borrower{firstname} + if defined $borrower{firstname} && !defined $borrower{preferred_name}; my %borrower_changes = DelUnchangedFields( $borrowernumber, %borrower ); $borrower_changes{'changed_fields'} = join ',', keys %borrower_changes; my $extended_attributes_changes = FilterUnchangedAttributes( $borrowernumber, $attributes ); diff --git a/t/db_dependent/Koha/Patron.t b/t/db_dependent/Koha/Patron.t index de956c4cd5a..3a553507a5a 100755 --- a/t/db_dependent/Koha/Patron.t +++ b/t/db_dependent/Koha/Patron.t @@ -19,7 +19,7 @@ use Modern::Perl; -use Test::More tests => 36; +use Test::More tests => 37; use Test::Exception; use Test::Warn; use Time::Fake; @@ -2620,3 +2620,61 @@ subtest 'Scrub the note fields' => sub { $schema->storage->txn_rollback; }; + +subtest 'preferred_name' => sub { + plan tests => 6; + + $schema->storage->txn_begin; + + my $tmp_patron = $builder->build_object( { class => 'Koha::Patrons' } ); + my $patron_data = $tmp_patron->unblessed; + $tmp_patron->delete; + delete $patron_data->{borrowernumber}; + delete $patron_data->{preferred_name}; + + my $patron = Koha::Patron->new( + + { + %$patron_data, + } + )->store; + + is( $patron->preferred_name, $patron->firstname, "Preferred name set to first name on creation when not defined"); + + $patron->delete; + + $patron_data->{preferred_name} = ""; + + $patron = Koha::Patron->new( + + { + %$patron_data, + } + )->store; + + is( $patron->preferred_name, $patron->firstname, "Preferred name set to first name on creation when empty string"); + + $patron->delete; + + $patron_data->{preferred_name} = "Preferred"; + $patron_data->{firstname} = "Not Preferred"; + + $patron = Koha::Patron->new( + + { + %$patron_data, + } + )->store; + + is( $patron->preferred_name, "Preferred", "Preferred name set when passed on creation"); + + $patron->preferred_name("")->store; + is( $patron->preferred_name, $patron->firstname, "Preferred name set to first name on update when empty string"); + + $patron->preferred_name(undef)->store(); + is( $patron->preferred_name, $patron->firstname, "Preferred name set to first name on update when undef"); + + $patron->preferred_name("Preferred again")->store(); + is( $patron->preferred_name, "Preferred again", "Preferred name set on update when passed"); + +}; diff --git a/t/db_dependent/api/v1/patrons.t b/t/db_dependent/api/v1/patrons.t index 0e3c73e21d9..d7f490b3b14 100755 --- a/t/db_dependent/api/v1/patrons.t +++ b/t/db_dependent/api/v1/patrons.t @@ -81,7 +81,7 @@ subtest 'list() tests' => sub { subtest 'librarian access tests' => sub { - plan tests => 21; + plan tests => 22; $schema->storage->txn_begin; @@ -264,7 +264,8 @@ subtest 'get() tests' => sub { $schema->storage->txn_rollback; subtest 'librarian access tests' => sub { - plan tests => 8; + + plan tests => 9; $schema->storage->txn_begin; -- 2.39.5