From 21b18679f6810f1e490da90ebd5d62bab5490481 Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
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.

Additionally it adds a method 'effective_name' to choose the preferred name if present
and fall back to firstname

The 'firstname' is displayed as 'First name' on the details page, with id/class
patron_first_name to allow ajusting via JS/CSS if library wants to highlight/hide
the non-preferred name

PatronAutoComplete/ysearch is updated to use 'effective_name' method

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

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
---
 Koha/Database/Columns.pm                      |  1 +
 Koha/Patron.pm                                | 13 ++++++++
 api/v1/swagger/paths/patrons.yaml             |  5 +++
 .../prog/en/includes/js-patron-format.inc     |  6 ++--
 .../prog/en/includes/patron-search.inc        |  4 +--
 .../prog/en/includes/patron-title.inc         | 13 ++++++--
 .../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 +++++++--
 t/db_dependent/Koha/Patron.t                  | 33 ++++++++++++++++++-
 15 files changed, 110 insertions(+), 17 deletions(-)

diff --git a/Koha/Database/Columns.pm b/Koha/Database/Columns.pm
index a7fb1a5e47c..d2ca808ec78 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 1691c9d25b5..c54e4f8d7d1 100644
--- a/Koha/Patron.pm
+++ b/Koha/Patron.pm
@@ -118,6 +118,19 @@ sub new {
     return $class->SUPER::new($params);
 }
 
+=head3 effective_name
+
+Return the preferred name for a patron, or their firstname if no
+preferred name is set
+
+=cut
+
+sub effective_name {
+    my ($self) = @_;
+
+    return $self->preferred_name || $self->firstname;
+}
+
 =head3 fixup_cardnumber
 
 Autogenerate next cardnumber from highest value found in database
diff --git a/api/v1/swagger/paths/patrons.yaml b/api/v1/swagger/paths/patrons.yaml
index 26ef12e0ce0..8835918ddd4 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 ae27dd09bab..549db7c0dbd 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,7 +20,9 @@
 
             var name;
             var firstname   = escape_str(patron.firstname);
+            var preferred_name = escape_str(patron.preferred_name);
             var surname     = escape_str(patron.surname);
+            var effective_name = preferred_name || firstname;
 
             if ( patron.middle_name != null && patron.middle_name != '' ) {
                 firstname += ' ' + escape_str(patron.middle_name);
@@ -30,10 +32,10 @@
                 firstname += ' (' + escape_str(patron.other_name) + ')';
             }
             if ( config && config.invert_name ) {
-                name = surname + ( firstname ? ', ' + firstname : '' );
+                name = surname + ( effective_name ? ', ' + effective_name : '' );
             }
             else {
-                name = firstname + ' ' + surname;
+                name = effective_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 c8dd64c1241..99cf512a3de 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-search.inc
@@ -493,7 +493,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 ) {
@@ -518,7 +518,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..0a2f5b4ccd1 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,8 @@
     [%- SET data.surname        = patron.surname -%]
     [%- SET data.othernames     = patron.othernames -%]
     [%- SET data.firstname      = patron.firstname -%]
+    [%- SET data.preferred_name = patron.preferred_name -%]
+    [%- SET data.effective_name = patron.effective_name -%]
     [%- SET data.middle_name    = patron.middle_name -%]
     [%- SET data.cardnumber     = patron.cardnumber -%]
     [%- SET data.borrowernumber = patron.borrowernumber -%]
@@ -16,6 +18,8 @@
     [%- SET data.surname        = borrower.surname -%]
     [%- SET data.othernames     = borrower.othernames -%]
     [%- SET data.firstname      = borrower.firstname -%]
+    [%- SET data.preferred_name = borrower.preferred_name -%]
+    [%- SET data.effective_name = borrower.effective_name -%]
     [%- SET data.middle_name    = borrower.middle_name -%]
     [%- SET data.cardnumber     = borrower.cardnumber -%]
     [%- SET data.borrowernumber = borrower.borrowernumber -%]
@@ -25,11 +29,16 @@
     [%- SET data.surname        = surname -%]
     [%- SET data.othernames     = othernames -%]
     [%- SET data.firstname      = firstname -%]
+    [%- SET data.preferred_name = preferred_name -%]
+    [%- SET data.effective_name = effective_name -%]
     [%- SET data.middle_name    = middle_name -%]
     [%- SET data.cardnumber     = cardnumber -%]
     [%- SET data.borrowernumber = borrowernumber -%]
     [%- SET data.title          = title -%]
 [%- END -%]
+[%- UNLESS data.effective_name -%]
+    [%- SET data.effective_name = data.preferred_name || data.firstname -%]
+[%- END -%]
 [%# Parameter no_html - if 1, the html tags are NOT generated -%]
 [%- IF no_title -%][%- SET data.title = "" -%][%- END -%]
 [%- IF data.title -%]
@@ -66,9 +75,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.effective_name ) -%], [% data.effective_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.effective_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 7f4ede15650..377561c3354 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 @@
         <label for="searchfieldstype_filter">Search field:</label>
         <select name="searchfieldstype" class="searchfieldstype_filter">
     [% END %]
-        [% SET standard = Koha.Preference('DefaultPatronSearchFields') || 'firstname|middle_name|surname|othernames|cardnumber|userid' %]
+        [% SET standard = Koha.Preference('DefaultPatronSearchFields') || 'firstname|preferred_name|middle_name|surname|othernames|cardnumber|userid' %]
         [%# Above is needed for adding fields from the DefaultPatronSearchFields preference to the dropdowns %]
         [% default_fields = [ 'standard', 'surname', 'cardnumber', 'all_emails', 'borrowernumber', 'userid', 'all_phones', 'full_address', 'dateofbirth', 'sort1', 'sort2' ] %]
         [% search_options = default_fields.merge(standard.split('\|')).unique %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
index 0c280f302b7..75496451bac 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
@@ -67,7 +67,7 @@ Patrons:
            type: modalselect
            source: borrowers
            exclusions: anonymized|auth_method|autorenew_checkouts|date_renewed|dateenrolled|dateexpiry|lang|lastseen|login_attempts|overdrive_auth_token|password|password_expiration_date|primary_contact_method|gonenoaddress|lost|debarred|debarredcomment|branchcode|categorycode|flags|guarantorid|relationship|privacy|privacy_guarantor_checkouts|privacy_guarantor_fines|pronouns|secret|sms_provider_id|updated_on|checkprevcheckout
-         - "If empty Koha will default to \"firstname|middle_name|surname|othernames|cardnumber|userid\". Additional fields added to this preference will be added as search options in the dropdown menu on the patron search page."
+         - "If empty Koha will default to \"firstname|preferred_name|middle_name|surname|othernames|cardnumber|userid\". Additional fields added to this preference will be added as search options in the dropdown menu on the patron search page."
      -
          - pref: DefaultPatronSearchMethod
            choices:
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
index 6877ef453f3..e25d6e6859d 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt
@@ -290,7 +290,7 @@ legend.collapsed i.fa.fa-caret-down::before {
                             [% END %]
 
                             [% IF ( step_1 ) %]
-                                [% UNLESS notitle && nosurname && nofirstname && nomiddle_name && nodateofbirth && noinitials && noothernames &&nosex && nopronouns %]
+                                [% UNLESS notitle && nosurname && nofirstname && nopreferred_name && nomiddle_name && nodateofbirth && noinitials && noothernames &&nosex && nopronouns %]
                                     <fieldset class="rows" id="memberentry_identity">
                                         <legend class="expanded" id="identity_lgd">
                                             <i class="fa fa-caret-down" title="Collapse this section"></i>
@@ -367,6 +367,21 @@ legend.collapsed i.fa.fa-caret-down::before {
                                                         [% END %]
                                                     </li>
                                                 [% END #/UNLESS nofirstname %]
+                                                [% UNLESS nopreferred_name %]
+                                                    <li>
+                                                        [% IF ( mandatorypreferred_name ) %]
+                                                            <label for="preferred_name" class="required">
+                                                        [% ELSE %]
+                                                            <label for="preferred_name">
+                                                        [% END %]
+                                                            Preferred name:
+                                                        </label>
+                                                        <input type="text" id="preferred_name" name="preferred_name" size="20"  value="[% borrower_data.preferred_name | html UNLESS opduplicate %]" />
+                                                        [% IF ( mandatorypreferred_name ) %]
+                                                            <span class="required">Required</span>
+                                                        [% END %]
+                                                    </li>
+                                                [% END #/UNLESS nopreferred_name %]
                                                 [% UNLESS nomiddle_name %]
                                                     <li>
                                                         [% 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 67155008d0f..91607b2f9db 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'          %]<span>Home library (branchcode)</span>
 [% CASE 'surname'             %]<span>Surname</span>
 [% CASE 'firstname'           %]<span>First name</span>
+[% CASE 'preferred_name'      %]<span>Preferred name</span>
 [% CASE 'middle_name'         %]<span>Middle name</span>
 [% CASE 'title'               %]<span>Title</span>
 [% CASE 'othernames'          %]<span>Other names</span>
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 e00506ab50f..05b088cc33e 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 @@
 
                                 <div class="rows">
                                     <ol>
+                                        [% IF ( patron.preferred_name && patron.firstname ) %]
+                                            <li id="patron_first_name">
+                                                <span class="label patron_first_name">First name: </span>
+                                                [% patron.firstname | html %]
+                                            </li>
+                                        [% END %]
                                         [% IF ( patron.phone ) %]
                                             <li>
                                                 <span class="label">Primary phone: </span>
@@ -225,7 +231,7 @@
                                                         [% IF logged_in_user.can_see_patron_infos( guarantee ) %]
                                                             <li><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% guarantee.borrowernumber | uri %]">[% guarantee.firstname | html %] [% guarantee.surname | html %]</a></li>
                                                         [% ELSE %]
-                                                            <li>[% guarantee.firstname | html %] [% guarantee.surname | html %]</li>
+                                                            <li>[% guarantee.effective_name | html %] [% guarantee.surname | html %]</li>
                                                         [% END %]
                                                     [% END %]
                                                 </ul>
@@ -241,7 +247,7 @@
                                                     [% FOREACH gr IN guarantor_relationships %]
                                                         [% SET guarantor = gr.guarantor %]
                                                         [% IF logged_in_user.can_see_patron_infos( guarantor ) %]
-                                                            <li><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% guarantor.id | uri %]">[% guarantor.firstname | html %] [% guarantor.surname | html %][% IF gr.relationship %] ([% gr.relationship | html %])[% END %]</a></li>
+                                                            <li><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% guarantor.id | uri %]">[% guarantor.effective_name | html %] [% guarantor.surname | html %][% IF gr.relationship %] ([% gr.relationship | html %])[% END %]</a></li>
                                                         [% 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 7be043165b0..46f6c54e90a 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/staff-global.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/staff-global.js
@@ -680,7 +680,7 @@ function patron_autocomplete(node, options) {
                     (item.link ? '<a href="' + item.link + '">' : "<a>") +
                     (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..4585e82205b 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 -%]
         <span class="patron-title">[% patron.title | html %]</span>
     [%- END -%]
-    [% patron.firstname | html %] [% patron.middle_name | html %] [% patron.surname | html %]
+    [% patron.effective_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 d3333eaeb34..d41014238d4 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.effective_name | html %] [% g.middle_name | html %] [% g.surname | html %]
                                                 [%- IF ! loop.last %], [% END %]
                                             [% END %]
                                         </span>
@@ -212,7 +212,7 @@
                 <form method="post" action="/cgi-bin/koha/opac-memberentry.pl" id="memberentry-form" autocomplete="off">
                     [% 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') %]
                         <div class="row">
                             <div class="col">
                                 <fieldset class="rows" id="memberentry_identity">
@@ -380,6 +380,15 @@
                                             </li>
                                         [% END %]
 
+                                        [% UNLESS hidden.defined('preferred_name') %]
+                                            <li>
+                                                <label for="borrower_preferred_name" class="[% required.preferred_name | html %]">Preferred name:</label>
+
+                                                <input type="text" id="borrower_preferred_name" name="borrower_preferred_name" value="[% borrower.preferred_name | html %]" class="[% required.preferred_name | html %]" />
+                                                <div class="required_label [% required.preferred_name | html %]">Required</div>
+                                            </li>
+                                        [% END %]
+
                                         [% UNLESS hidden.defined('dateofbirth') %]
                                             <li>
                                                 <label for="borrower_dateofbirth" class="[% required.dateofbirth | html %]">Date of birth:</label>
diff --git a/t/db_dependent/Koha/Patron.t b/t/db_dependent/Koha/Patron.t
index bad1cc1bfaf..e2dd2387cc8 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 => 33;
+use Test::More tests => 34;
 use Test::Exception;
 use Test::Warn;
 use Time::Fake;
@@ -2279,3 +2279,34 @@ subtest 'Scrub the note fields' => sub {
 
     $schema->storage->txn_rollback;
 };
+
+subtest 'effective_name() tests' => sub {
+
+    plan tests => 2;
+
+    $schema->storage->txn_begin;
+
+    my $patron_1 = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => {
+                firstname      => 'John',
+                preferred_name => 'Jacob',
+            }
+        }
+    );
+    my $patron_2 = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => {
+                firstname      => "Bob",
+                preferred_name => undef,
+            }
+        }
+    );
+
+    is( $patron_1->effective_name, "Jacob", 'Preferred name correctly chosen' );
+    is( $patron_2->effective_name, "Bob",   'First name correctly chosen if no preferred name' );
+
+    $schema->storage->txn_rollback;
+};
-- 
2.45.0