[open-ils-commits] [GIT] Evergreen ILS branch master updated. 647710dbdbd153b7d06ecfcc36ddcf97a933851a

Evergreen Git git at git.evergreen-ils.org
Tue Sep 4 13:25:39 EDT 2018


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  647710dbdbd153b7d06ecfcc36ddcf97a933851a (commit)
       via  5ccc7d919ac3317d14cca34efc65cbe17c77c50e (commit)
       via  7908f3c3eca891bd4d6319447f3a94c71ee7be49 (commit)
       via  ab3d64d4a611e0a4674e02e304cf633dc6c3855f (commit)
       via  f8789b3767b4eabfa68b8336804c583c8ade5f50 (commit)
       via  8d0f5b6f4e65a6c77f2f943b29fc2642ac85dcab (commit)
      from  8dfe18454b87addd05a9cc867e7712b9f32bfbe3 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 647710dbdbd153b7d06ecfcc36ddcf97a933851a
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date:   Tue Sep 4 13:32:46 2018 -0400

    LP#1776020: stamp database update
    
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index d975d9a..43c1903 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -92,7 +92,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1121', :eg_version); -- khuckins/berick/kmlussier
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1122', :eg_version); -- berick/kmlussier/gmcharlt
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql b/Open-ILS/src/sql/Pg/upgrade/1122.schema.patron-alt-name.sql
similarity index 99%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql
rename to Open-ILS/src/sql/Pg/upgrade/1122.schema.patron-alt-name.sql
index 9fb2726..0d2eca7 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/1122.schema.patron-alt-name.sql
@@ -1,10 +1,7 @@
-
-/**
-update patron merge
-*/
-
 BEGIN;
 
+SELECT evergreen.upgrade_deps_block_check('1122', :eg_version);
+
 ALTER TABLE actor.usr 
     ADD COLUMN pref_prefix TEXT,
     ADD COLUMN pref_first_given_name TEXT,

commit 5ccc7d919ac3317d14cca34efc65cbe17c77c50e
Author: Bill Erickson <berickxx at gmail.com>
Date:   Mon Aug 27 22:49:12 2018 -0400

    LP#1776020 Deduplicate name keywords in patron merge
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>

diff --git a/Open-ILS/src/sql/Pg/999.functions.global.sql b/Open-ILS/src/sql/Pg/999.functions.global.sql
index 927e47c..da91959 100644
--- a/Open-ILS/src/sql/Pg/999.functions.global.sql
+++ b/Open-ILS/src/sql/Pg/999.functions.global.sql
@@ -365,12 +365,24 @@ BEGIN
         pref_family_name = 
             COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
         pref_suffix = 
-            COALESCE(pref_suffix, (SELECT pref_suffix FROM susr)),
-        name_keywords =
-            COALESCE(name_keywords, '') || ' ' ||
-                COALESCE((SELECT name_keywords FROM susr), '')
+            COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
     WHERE id = dest_usr;
 
+    -- Copy and deduplicate name keywords
+    -- String -> array -> rows -> DISTINCT -> array -> string
+    WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
+         dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
+    UPDATE actor.usr SET name_keywords = (
+        WITH keywords AS (
+            SELECT DISTINCT UNNEST(
+                REGEXP_SPLIT_TO_ARRAY(
+                    COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
+                    COALESCE((SELECT name_keywords FROM dusr), ''),  E'\\s+'
+                )
+            ) AS parts
+        ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw
+    ) WHERE id = dest_usr;
+
     -- Finally, delete the source user
     DELETE FROM actor.usr WHERE id = src_usr;
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql
index 6eea68e..9fb2726 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql
@@ -405,12 +405,24 @@ BEGIN
         pref_family_name = 
             COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
         pref_suffix = 
-            COALESCE(pref_suffix, (SELECT pref_suffix FROM susr)),
-        name_keywords =
-            COALESCE(name_keywords, '') || ' ' ||
-                COALESCE((SELECT name_keywords FROM susr), '')
+            COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
     WHERE id = dest_usr;
 
+    -- Copy and deduplicate name keywords
+    -- String -> array -> rows -> DISTINCT -> array -> string
+    WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
+         dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
+    UPDATE actor.usr SET name_keywords = (
+        WITH keywords AS (
+            SELECT DISTINCT UNNEST(
+                REGEXP_SPLIT_TO_ARRAY(
+                    COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
+                    COALESCE((SELECT name_keywords FROM dusr), ''),  E'\\s+'
+                )
+            ) AS parts
+        ) SELECT ARRAY_TO_STRING(ARRAY_AGG(kw.parts), ' ') FROM keywords kw
+    ) WHERE id = dest_usr;
+
     -- Finally, delete the source user
     DELETE FROM actor.usr WHERE id = src_usr;
 

commit 7908f3c3eca891bd4d6319447f3a94c71ee7be49
Author: Bill Erickson <berickxx at gmail.com>
Date:   Mon Aug 27 22:00:52 2018 -0400

    LP#1776020 Add pref names to bills and items-out receipt templates
    
    Add support (with inline docs) for pref_.* name fields in Bills
    Current/Historical/Payment templates and the patron Items Out template.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>

diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_bill_payment.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_bill_payment.tt2
index d0189c9..0147d50 100644
--- a/Open-ILS/src/templates/staff/share/print_templates/t_bill_payment.tt2
+++ b/Open-ILS/src/templates/staff/share/print_templates/t_bill_payment.tt2
@@ -8,6 +8,7 @@ includes:
   * second_given_name
   * family_name
   * suffix
+  * pref_ versions of all name fields (e.g. pref_family_name);
   * card.barcode
   * expire_date
   * alias - aka Holds Alias
diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_bills_current.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_bills_current.tt2
index 786b868..c2b287f 100644
--- a/Open-ILS/src/templates/staff/share/print_templates/t_bills_current.tt2
+++ b/Open-ILS/src/templates/staff/share/print_templates/t_bills_current.tt2
@@ -17,6 +17,7 @@ to this template includes:
     patron.alias - The patron's alias
     patron.has_email - Whether or not the patron has an email address
     patron.has_phone - Whether or not the patron has a phone number
+    pref_ versions of all name fields (e.g. pref_family_name);
 * transasctions - a list of transactions, each of which
   contains:
     xact.summary - information about the transaction
diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_bills_historical.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_bills_historical.tt2
index ed789a5..d1caaf2 100644
--- a/Open-ILS/src/templates/staff/share/print_templates/t_bills_historical.tt2
+++ b/Open-ILS/src/templates/staff/share/print_templates/t_bills_historical.tt2
@@ -17,6 +17,7 @@ to this template includes:
     patron.alias - The patron's alias
     patron.has_email - Whether or not the patron has an email address
     patron.has_phone - Whether or not the patron has a phone number
+    pref_ versions of all name fields (e.g. pref_family_name);
 * transasctions - a list of transactions, each of which contains:
   xact.id - Bill unique id
   xact.copy_barcode - Copy barcode
diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_items_out.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_items_out.tt2
index 225fbb4..2092ad9 100644
--- a/Open-ILS/src/templates/staff/share/print_templates/t_items_out.tt2
+++ b/Open-ILS/src/templates/staff/share/print_templates/t_items_out.tt2
@@ -8,6 +8,7 @@ Fields include:
   * second_given_name
   * family_name
   * suffix
+  * pref_ versions of all name fields (e.g. pref_family_name);
   * card.barcode
   * money_summary.balance_owed - current balance
   * money_summary.total_paid - payments made on outstanding fines/fees
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
index 8f4157f..ceda5c8 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
@@ -442,6 +442,11 @@ function($scope , $q , $routeParams , egCore , egConfirmDialog , $location,
             second_given_name : cusr.second_given_name(),
             family_name : cusr.family_name(),
             suffix : cusr.suffix(),
+            pref_prefix : cusr.pref_prefix(),
+            pref_first_given_name : cusr.pref_first_given_name(),
+            pref_second_given_name : cusr.pref_second_given_name(),
+            pref_family_name : cusr.pref_family_name(),
+            pref_suffix : cusr.pref_suffix(),
             card : { barcode : cusr.card().barcode() },
             expire_date : cusr.expire_date(),
             alias : cusr.alias(),
@@ -574,6 +579,11 @@ function($scope , $q , $routeParams , egCore , egConfirmDialog , $location,
                             second_given_name : cusr.second_given_name(),
                             family_name : cusr.family_name(),
                             suffix : cusr.suffix(),
+                            pref_prefix : cusr.pref_prefix(),
+                            pref_first_given_name : cusr.pref_first_given_name(),
+                            pref_second_given_name : cusr.pref_second_given_name(),
+                            pref_family_name : cusr.pref_family_name(),
+                            pref_suffix : cusr.pref_suffix(),
                             card : { barcode : cusr.card().barcode() },
                             expire_date : cusr.expire_date(),
                             alias : cusr.alias(),
@@ -1064,6 +1074,11 @@ function($scope,  $q , egCore , patronSvc , billSvc , egPromptDialog , $location
                             egCore.org.get(egCore.auth.user().ws_ou())),
                         patron : {
                             prefix : cusr.prefix(),
+                            pref_prefix : cusr.pref_prefix(),
+                            pref_first_given_name : cusr.pref_first_given_name(),
+                            pref_second_given_name : cusr.pref_second_given_name(),
+                            pref_family_name : cusr.pref_family_name(),
+                            pref_suffix : cusr.pref_suffix(),
                             first_given_name : cusr.first_given_name(),
                             second_given_name : cusr.second_given_name(),
                             family_name : cusr.family_name(),
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/items_out.js b/Open-ILS/web/js/ui/default/staff/circ/patron/items_out.js
index c836690..105c9f5 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/items_out.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/items_out.js
@@ -380,6 +380,11 @@ function($scope , $q , $routeParams , $timeout , egCore , egUser , patronSvc ,
             second_given_name : cusr.second_given_name(),
             family_name : cusr.family_name(),
             suffix : cusr.suffix(),
+            pref_prefix : cusr.pref_prefix(),
+            pref_first_given_name : cusr.pref_first_given_name(),
+            pref_second_given_name : cusr.pref_second_given_name(),
+            pref_family_name : cusr.pref_family_name(),
+            pref_suffix : cusr.pref_suffix(),
             card : { barcode : cusr.card().barcode() },
             money_summary : patronSvc.patron_stats.fines,
             expire_date : cusr.expire_date(),

commit ab3d64d4a611e0a4674e02e304cf633dc6c3855f
Author: Kathy Lussier <klussier at masslnc.org>
Date:   Mon Aug 27 16:15:31 2018 -0400

    LP#1776020: Tweaks to display of name keywords
    
    Add an icon next to the primary name to indicate the presence of name keywords,
    which can be seen via hovering. The name keywords continue to display at the
    bottom of the patron summary sidebar.
    
    Also fixes a minor bug where preferred name would display for anyone with
    a middle name.
    
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>

diff --git a/Open-ILS/src/templates/staff/circ/patron/index.tt2 b/Open-ILS/src/templates/staff/circ/patron/index.tt2
index dc9c1b2..6724ca4 100644
--- a/Open-ILS/src/templates/staff/circ/patron/index.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/index.tt2
@@ -101,6 +101,7 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
                 '{{patron().family_name()}}',
                 '{{patron().first_given_name()}}',
                 '{{patron().second_given_name()}}') %]
+               <span ng-if="patron().name_keywords()"> <a title="[% l('Name keywords: ') %]{{patron().name_keywords()}}" class="glyphicon glyphicon-tags"></a>
           </div>
 
           <p ng-show="patron().notes().length > 0"><a class='patron-summary-has-notes' href="./circ/patron/{{patron().id()}}/notes"><span class="label label-warning">Notes  {{patron().notes().length}}</span></a></p>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
index cf0bc4e..c50ff0d 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
@@ -2,7 +2,7 @@
 <div ng-cloak class="patron-summary-grid-wrapper">
   <div ng-show="patron()" id="patron-summary-grid">
     <div class="row patron-summary-pref-name"
-      ng-if="patron().pref_family_name() || patron().pref_first_given_name() || patron().second_given_name()">
+      ng-if="patron().pref_family_name() || patron().pref_first_given_name() || patron().pref_second_given_name()">
       <div class="col-md-12">
         [% l('[_1], [_2] [_3] (Preferred)', 
           '{{patron().pref_family_name() || patron().family_name()}}',
@@ -11,11 +11,6 @@
         %]
       </div>
     </div>
-    <div ng-if="patron().name_keywords()" class="row patron-summary-pref-name">
-      <div class="col-md-12">
-        <a title="{{patron().name_keywords()}}">[% l('Name Keywords') %]</a>
-      </div>
-    </div>
     <div class="row" 
       ng-class="{'patron-summary-divider' : !$index}"
       ng-repeat="penalty in alert_penalties()">
diff --git a/Open-ILS/src/templates/staff/css/circ.css.tt2 b/Open-ILS/src/templates/staff/css/circ.css.tt2
index f0d29c2..cbbf509 100644
--- a/Open-ILS/src/templates/staff/css/circ.css.tt2
+++ b/Open-ILS/src/templates/staff/css/circ.css.tt2
@@ -22,6 +22,13 @@ but the ones I'm finding aren't quite cutting it..*/
   color: #5cb85c;
 }
 
+/* Tag icon used for name keywords shouldn't look like a link */
+
+.glyphicon-tags, .glyphicon-tags:hover, .glyphicon-tags:focus {
+  color: #333;
+  text-decoration: none;
+}
+
 /* FIXME: use .barcode instead */
 #patron-checkout-barcode,
 #patron-renewal-barcode,

commit f8789b3767b4eabfa68b8336804c583c8ade5f50
Author: Bill Erickson <berickxx at gmail.com>
Date:   Tue Aug 21 21:31:15 2018 -0400

    LP#1776020 Release notes for pref name / keywords
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>

diff --git a/docs/RELEASE_NOTES_NEXT/Circulation/patron-pref-name.adoc b/docs/RELEASE_NOTES_NEXT/Circulation/patron-pref-name.adoc
new file mode 100644
index 0000000..2276331
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/Circulation/patron-pref-name.adoc
@@ -0,0 +1,36 @@
+Patron Preferred Name and Name Search Keywords
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Preferred Name
+++++++++++++++
+
+Adds a new set of patron preferred name fields for prefix, first,
+middle, last, and suffix allowing patrons to provide preferred name
+information.  Preferred names are optional and each acts as an overlay
+to the analogous primary name field, making it possible to provide
+preferred name values for individual fields.
+
+For example, a patron named William Erickson may have a preferred first
+name (pref_first_given_name) of Bill, in which case the preferred name
+would be Bill Erickson.  Note a preferred last name is not required in
+this case as the code uses primary name values as defaults when not
+replaced with a preferred version.
+
+* Patrons will see primary names displayed in the catalog when set.
+* Staff will see both primary name and preferred name in the patron
+  summary side bar.
+* Patron searches for any given name field will search both the primary
+  and preferred name data.
+* Preferred name fields are available in Action/Trigger templates and
+  are present in various patron-focused print templates.
+
+Name Keywords
+++++++++++++++
+
+Adds a new field to store miscellaneous patron name search terms.  These
+values are only for searching and do not appear in any interfaces, apart
+from the patron summary side bar and the patron edit UI.
+
+Included is a new search field in the patron search UI which searches
+keyword values and all other name fields.  It's essentially a global patron
+name keyword search.

commit 8d0f5b6f4e65a6c77f2f943b29fc2642ac85dcab
Author: Bill Erickson <berickxx at gmail.com>
Date:   Sun May 20 22:15:23 2018 -0400

    LP#1776020 Patron preferred name & name keywords
    
    Preferred Name
    
    Adds a new set of patron preferred name fields for prefix, first,
    middle, last, and suffix allowing patrons to provide preferred name
    information.  Preferred names are optional and each acts as an overlay
    to the analogous primary name field, making it possible to provide
    preferred name values for individual fields.
    
    For example, a patron named William Erickson may have a preferred first
    name (pref_first_given_name) of Bill, in which case the preferred name
    would be Bill Erickson.  Note a preferred last name is not required in
    this case as the code uses primary name values as defaults when not
    replaced with a preferred version.
    
    * Patrons will see primary names displayed in the catalog when set.
    * Staff will see both primary name and preferred name in the patron
      summary side bar.
    * Patron searches for any given name field will search both the primary
      and preferred name data.
    * Preferred name fields are available in Action/Trigger templates and
      are present in various patron-focused print templates.
    
    Name Keywords
    
    Adds a new field to store miscellaneous patron name search terms.  These
    values are only for searching and do not appear in any interfaces, apart
    from the patron summary side bar and the patron edit UI.
    
    Included is a new search field in the patron search UI which searches
    keyword values and all other name fields.  It's essentially a global patron
    name keyword search.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index e60c5de..9436ceb 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -3591,6 +3591,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			<field reporter:label="OPAC/Staff Client Holds Alias" name="alias"  reporter:datatype="text"/>
 			<field reporter:label="Juvenile" name="juvenile"  reporter:datatype="bool"/>
 			<field reporter:label="Record Last Update Time" name="last_update_time" reporter:datatype="timestamp"/>
+			<field reporter:label="Preferred Prefix" name="pref_prefix" reporter:datatype="text"/>
+			<field reporter:label="Preferred First Name" name="pref_first_given_name" reporter:datatype="text"/>
+			<field reporter:label="Preferred Middle Name" name="pref_second_given_name" reporter:datatype="text"/>
+			<field reporter:label="Preferred Last Name" name="pref_family_name"  reporter:datatype="text"/>
+			<field reporter:label="Preferred Suffix" name="pref_suffix" reporter:datatype="text"/>
+			<field reporter:label="Name Keywords" name="name_keywords" reporter:datatype="text"/>
 			<field reporter:label="Additional Permission Groups" name="groups" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Is Deleted" name="deleted" reporter:datatype="bool"/>
 			<field reporter:label="User Notes" name="notes" oils_persist:virtual="true" reporter:datatype="link"/>
@@ -10382,6 +10388,9 @@ SELECT  usr,
             <field reporter:label="Date of Birth" name="dob" reporter:datatype="text"/>
             <field reporter:label="Complete" name="complete" reporter:datatype="bool"/>
             <field reporter:label="Requesting User" name="requesting_usr" reporter:datatype="link"/>
+						<field reporter:label="Preferred First Name" name="pref_first_given_name" reporter:datatype="text"/>
+						<field reporter:label="Preferred Middle Name" name="pref_second_given_name" reporter:datatype="text"/>
+						<field reporter:label="Preferred Last Name" name="pref_family_name"  reporter:datatype="text"/>
         </fields>
         <links>
             <link field="requesting_usr" reltype="has_a" key="id" map="" class="au"/>
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/actor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/actor.pm
index 1cef88b..686d960 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/actor.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/actor.pm
@@ -690,15 +690,45 @@ sub patron_search {
     my $dob;
     my @dobv;
 
-    if ($diacritic_insensitive) {
-       $usr = join ' AND ', map { "evergreen.unaccent_and_squash(CAST($_ AS text)) ~ ?" } grep { ''.$$search{$_}{group} eq '0' } keys %$search;
-       @usrv = map { "^" . _prepare_name_argument($$search{$_}{value}) } grep { ''.$$search{$_}{group} eq '0' } keys %$search;
+    # Compile the WHERE component of the actor.usr fields.
+    # When a name field is encountered, search both the name field and
+    # the alternate version of the name field.
+    my @name_fields = qw/prefix first_given_name second_given_name family_name suffix/;
+    my @usr_where_parts;
 
-    } else {
-       $usr = join ' AND ', map { "evergreen.lowercase(CAST($_ AS text)) ~ ?" } grep { ''.$$search{$_}{group} eq '0' } keys %$search;
-       @usrv = map { "^" . _clean_regex_chars($$search{$_}{value}) } grep { ''.$$search{$_}{group} eq '0' } keys %$search;
+    my @usr_fields = grep { ''.$$search{$_}{group} eq '0' } keys %$search;
+    for my $usr_field (@usr_fields) {
+
+        # sprintf template
+        my $where_func = $diacritic_insensitive ?
+            "evergreen.unaccent_and_squash(CAST(%s AS text)) ~ ?" :
+            "evergreen.lowercase(CAST(%s AS text)) ~ ?";
+
+        my $val = $diacritic_insensitive ?
+            "^" . _prepare_name_argument($$search{$usr_field}{value}) :
+            "^" . _clean_regex_chars($$search{$usr_field}{value});
+
+        if (grep {$_ eq $usr_field} @name_fields) {
+            # When searching a name field include an OR search
+            # on the alternate version of the same field.
+
+            push(@usr_where_parts, sprintf(
+                "($where_func OR $where_func)", $usr_field, "pref_$usr_field")
+            );
+
+            # search main field and alt name field with same value.
+            push(@usrv, $val);
+            push(@usrv, $val);
+
+        } else {
+
+            push(@usr_where_parts, sprintf($where_func, $usr_field));
+            push(@usrv, $val);
+        }
     }
 
+    $usr = join ' AND ', @usr_where_parts;
+
     while (($key, $value) = each (%$search)) {
         if($$search{$key}{group} eq '4') {
             my $tval = $key;
@@ -760,19 +790,23 @@ sub patron_search {
         $ident = '(' . join(' OR ', @is) . ')';
     }
 
+    # name keywords search
     my $name = '';
     my @ns;
     my @namev;
-    if (0 && $nv) {
-        for my $n ( qw/first_given_name second_given_name family_name/ ) {
-            if ($diacritic_insensitive) {
-                push @ns, "evergreen.unaccent_and_squash($n) ~ ?";
-            } else {
-                push @ns, "evergreen.lowercase($n) ~ ?";
-            }
-            push @namev, "^$nv";
-        }
-        $name = '(' . join(' OR ', @ns) . ')';
+    if ($nv) {
+        $name = "name_kw_tsvector @@ to_tsquery(?)";
+
+        # Remove characters that to_tsquery might treat as operators.
+        # Note using plainto_tsquery to ignore operators won't let us
+        # also do prefix matching.
+        $nv =~ s/[^\w\s\.\-']//g;
+
+        my @parts = split(' ', $nv);
+
+        # tsquery on multiple names joined w/ '&'
+        # Adding :* gives us prefix matching
+        push @namev, join(' & ', map { "$_:*" } @parts);
     }
 
     my $profile = '';
diff --git a/Open-ILS/src/sql/Pg/005.schema.actors.sql b/Open-ILS/src/sql/Pg/005.schema.actors.sql
index 1152d97..61d5bae 100644
--- a/Open-ILS/src/sql/Pg/005.schema.actors.sql
+++ b/Open-ILS/src/sql/Pg/005.schema.actors.sql
@@ -42,6 +42,13 @@ CREATE TABLE actor.usr (
 	second_given_name	TEXT,
 	family_name		TEXT				NOT NULL,
 	suffix			TEXT,
+    pref_prefix TEXT,
+    pref_first_given_name TEXT,
+    pref_second_given_name TEXT,
+    pref_family_name TEXT,
+    pref_suffix TEXT,
+    name_keywords TEXT,
+    name_kw_tsvector TSVECTOR,
 	alias			TEXT,
 	day_phone		TEXT,
 	evening_phone		TEXT,
@@ -87,6 +94,13 @@ CREATE INDEX actor_usr_second_given_name_unaccent_idx ON actor.usr (evergreen.un
 CREATE INDEX actor_usr_family_name_unaccent_idx ON actor.usr (evergreen.unaccent_and_squash(family_name));
 CREATE INDEX actor_usr_usrname_unaccent_idx ON actor.usr (evergreen.unaccent_and_squash(usrname));
 
+CREATE INDEX actor_usr_pref_first_given_name_idx ON actor.usr (evergreen.lowercase(pref_first_given_name));
+CREATE INDEX actor_usr_pref_second_given_name_idx ON actor.usr (evergreen.lowercase(pref_second_given_name));
+CREATE INDEX actor_usr_pref_family_name_idx ON actor.usr (evergreen.lowercase(pref_family_name));
+CREATE INDEX actor_usr_pref_first_given_name_unaccent_idx ON actor.usr (evergreen.unaccent_and_squash(pref_first_given_name));
+CREATE INDEX actor_usr_pref_second_given_name_unaccent_idx ON actor.usr (evergreen.unaccent_and_squash(pref_second_given_name));
+CREATE INDEX actor_usr_pref_family_name_unaccent_idx ON actor.usr (evergreen.unaccent_and_squash(pref_family_name));
+
 CREATE INDEX actor_usr_usrname_idx ON actor.usr (evergreen.lowercase(usrname));
 CREATE INDEX actor_usr_email_idx ON actor.usr (evergreen.lowercase(email));
 
@@ -144,6 +158,37 @@ CREATE TRIGGER actor_crypt_pw_insert_trigger
 
 CREATE RULE protect_user_delete AS ON DELETE TO actor.usr DO INSTEAD UPDATE actor.usr SET deleted = TRUE WHERE OLD.id = actor.usr.id;
 
+CREATE OR REPLACE FUNCTION actor.user_ingest_name_keywords() 
+    RETURNS TRIGGER AS $func$
+BEGIN
+    NEW.name_kw_tsvector := TO_TSVECTOR(
+        COALESCE(NEW.prefix, '')                || ' ' || 
+        COALESCE(NEW.first_given_name, '')      || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.first_given_name), '') || ' ' || 
+        COALESCE(NEW.second_given_name, '')     || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.second_given_name), '') || ' ' || 
+        COALESCE(NEW.family_name, '')           || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.family_name), '') || ' ' || 
+        COALESCE(NEW.suffix, '')                || ' ' || 
+        COALESCE(NEW.pref_prefix, '')            || ' ' || 
+        COALESCE(NEW.pref_first_given_name, '')  || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.pref_first_given_name), '') || ' ' || 
+        COALESCE(NEW.pref_second_given_name, '') || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.pref_second_given_name), '') || ' ' || 
+        COALESCE(NEW.pref_family_name, '')       || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.pref_family_name), '') || ' ' || 
+        COALESCE(NEW.pref_suffix, '')            || ' ' || 
+        COALESCE(NEW.name_keywords, '')
+    );
+    RETURN NEW;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+-- Add after the batch upate above to avoid duplicate updates.
+CREATE TRIGGER user_ingest_name_keywords_tgr 
+    BEFORE INSERT OR UPDATE ON actor.usr 
+    FOR EACH ROW EXECUTE PROCEDURE actor.user_ingest_name_keywords();
+
 CREATE TABLE actor.usr_note (
 	id		BIGSERIAL			PRIMARY KEY,
 	usr		BIGINT				NOT NULL REFERENCES actor.usr ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
diff --git a/Open-ILS/src/sql/Pg/015.schema.staging.sql b/Open-ILS/src/sql/Pg/015.schema.staging.sql
index 4dfc3ee..ddd3b71 100644
--- a/Open-ILS/src/sql/Pg/015.schema.staging.sql
+++ b/Open-ILS/src/sql/Pg/015.schema.staging.sql
@@ -15,6 +15,9 @@ CREATE TABLE staging.user_stage (
         first_given_name        TEXT,
         second_given_name       TEXT,
         family_name             TEXT,
+        pref_first_given_name   TEXT,
+        pref_second_given_name  TEXT,
+        pref_family_name        TEXT,
         day_phone               TEXT,
         evening_phone           TEXT,
         home_ou                 INT DEFAULT 2,
diff --git a/Open-ILS/src/sql/Pg/999.functions.global.sql b/Open-ILS/src/sql/Pg/999.functions.global.sql
index 521e965..927e47c 100644
--- a/Open-ILS/src/sql/Pg/999.functions.global.sql
+++ b/Open-ILS/src/sql/Pg/999.functions.global.sql
@@ -352,12 +352,33 @@ BEGIN
         -- do nothing
     END;
 
+    -- propagate preferred name values from the source user to the
+    -- destination user, but only when values are not being replaced.
+    WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
+    UPDATE actor.usr SET 
+        pref_prefix = 
+            COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
+        pref_first_given_name = 
+            COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
+        pref_second_given_name = 
+            COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
+        pref_family_name = 
+            COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
+        pref_suffix = 
+            COALESCE(pref_suffix, (SELECT pref_suffix FROM susr)),
+        name_keywords =
+            COALESCE(name_keywords, '') || ' ' ||
+                COALESCE((SELECT name_keywords FROM susr), '')
+    WHERE id = dest_usr;
+
     -- Finally, delete the source user
     DELETE FROM actor.usr WHERE id = src_usr;
 
 END;
 $$ LANGUAGE plpgsql;
 
+
+
 COMMENT ON FUNCTION actor.usr_merge(INT, INT, BOOLEAN, BOOLEAN, BOOLEAN) IS $$
 Merges all user date from src_usr to dest_usr.  When collisions occur, 
 keep dest_usr's data and delete src_usr's data.
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql
new file mode 100644
index 0000000..6eea68e
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql
@@ -0,0 +1,422 @@
+
+/**
+update patron merge
+*/
+
+BEGIN;
+
+ALTER TABLE actor.usr 
+    ADD COLUMN pref_prefix TEXT,
+    ADD COLUMN pref_first_given_name TEXT,
+    ADD COLUMN pref_second_given_name TEXT,
+    ADD COLUMN pref_family_name TEXT,
+    ADD COLUMN pref_suffix TEXT,
+    ADD COLUMN name_keywords TEXT,
+    ADD COLUMN name_kw_tsvector TSVECTOR;
+
+ALTER TABLE staging.user_stage
+    ADD COLUMN pref_first_given_name TEXT,
+    ADD COLUMN pref_second_given_name TEXT,
+    ADD COLUMN pref_family_name TEXT;
+
+CREATE INDEX actor_usr_pref_first_given_name_idx 
+    ON actor.usr (evergreen.lowercase(pref_first_given_name));
+CREATE INDEX actor_usr_pref_second_given_name_idx 
+    ON actor.usr (evergreen.lowercase(pref_second_given_name));
+CREATE INDEX actor_usr_pref_family_name_idx 
+    ON actor.usr (evergreen.lowercase(pref_family_name));
+CREATE INDEX actor_usr_pref_first_given_name_unaccent_idx 
+    ON actor.usr (evergreen.unaccent_and_squash(pref_first_given_name));
+CREATE INDEX actor_usr_pref_second_given_name_unaccent_idx 
+    ON actor.usr (evergreen.unaccent_and_squash(pref_second_given_name));
+CREATE INDEX actor_usr_pref_family_name_unaccent_idx 
+   ON actor.usr (evergreen.unaccent_and_squash(pref_family_name));
+
+-- Update keyword indexes for existing patrons
+
+UPDATE actor.usr SET name_kw_tsvector = 
+    TO_TSVECTOR(
+        COALESCE(prefix, '') || ' ' || 
+        COALESCE(first_given_name, '') || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(first_given_name), '') || ' ' || 
+        COALESCE(second_given_name, '') || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(second_given_name), '') || ' ' || 
+        COALESCE(family_name, '') || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(family_name), '') || ' ' || 
+        COALESCE(suffix, '')
+    );
+
+CREATE OR REPLACE FUNCTION actor.user_ingest_name_keywords() 
+    RETURNS TRIGGER AS $func$
+BEGIN
+    NEW.name_kw_tsvector := TO_TSVECTOR(
+        COALESCE(NEW.prefix, '')                || ' ' || 
+        COALESCE(NEW.first_given_name, '')      || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.first_given_name), '') || ' ' || 
+        COALESCE(NEW.second_given_name, '')     || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.second_given_name), '') || ' ' || 
+        COALESCE(NEW.family_name, '')           || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.family_name), '') || ' ' || 
+        COALESCE(NEW.suffix, '')                || ' ' || 
+        COALESCE(NEW.pref_prefix, '')            || ' ' || 
+        COALESCE(NEW.pref_first_given_name, '')  || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.pref_first_given_name), '') || ' ' || 
+        COALESCE(NEW.pref_second_given_name, '') || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.pref_second_given_name), '') || ' ' || 
+        COALESCE(NEW.pref_family_name, '')       || ' ' || 
+        COALESCE(evergreen.unaccent_and_squash(NEW.pref_family_name), '') || ' ' || 
+        COALESCE(NEW.pref_suffix, '')            || ' ' || 
+        COALESCE(NEW.name_keywords, '')
+    );
+    RETURN NEW;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+-- Add after the batch upate above to avoid duplicate updates.
+CREATE TRIGGER user_ingest_name_keywords_tgr 
+    BEFORE INSERT OR UPDATE ON actor.usr 
+    FOR EACH ROW EXECUTE PROCEDURE actor.user_ingest_name_keywords();
+
+
+-- merge pref names from source user to target user, except when
+-- clobbering existing pref names.
+CREATE OR REPLACE FUNCTION actor.usr_merge(src_usr INT, dest_usr INT, 
+    del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) 
+    RETURNS VOID AS $$
+DECLARE
+	suffix TEXT;
+	bucket_row RECORD;
+	picklist_row RECORD;
+	queue_row RECORD;
+	folder_row RECORD;
+BEGIN
+
+    -- do some initial cleanup 
+    UPDATE actor.usr SET card = NULL WHERE id = src_usr;
+    UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
+    UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
+
+    -- actor.*
+    IF del_cards THEN
+        DELETE FROM actor.card where usr = src_usr;
+    ELSE
+        IF deactivate_cards THEN
+            UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
+        END IF;
+        UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
+    END IF;
+
+
+    IF del_addrs THEN
+        DELETE FROM actor.usr_address WHERE usr = src_usr;
+    ELSE
+        UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
+    END IF;
+
+    UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
+    -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
+    UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
+    PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
+    PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
+
+    -- permission.*
+    PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
+    PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
+    PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
+    PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
+
+
+    -- container.*
+	
+	-- For each *_bucket table: transfer every bucket belonging to src_usr
+	-- into the custody of dest_usr.
+	--
+	-- In order to avoid colliding with an existing bucket owned by
+	-- the destination user, append the source user's id (in parenthesese)
+	-- to the name.  If you still get a collision, add successive
+	-- spaces to the name and keep trying until you succeed.
+	--
+	FOR bucket_row in
+		SELECT id, name
+		FROM   container.biblio_record_entry_bucket
+		WHERE  owner = src_usr
+	LOOP
+		suffix := ' (' || src_usr || ')';
+		LOOP
+			BEGIN
+				UPDATE  container.biblio_record_entry_bucket
+				SET     owner = dest_usr, name = name || suffix
+				WHERE   id = bucket_row.id;
+			EXCEPTION WHEN unique_violation THEN
+				suffix := suffix || ' ';
+				CONTINUE;
+			END;
+			EXIT;
+		END LOOP;
+	END LOOP;
+
+	FOR bucket_row in
+		SELECT id, name
+		FROM   container.call_number_bucket
+		WHERE  owner = src_usr
+	LOOP
+		suffix := ' (' || src_usr || ')';
+		LOOP
+			BEGIN
+				UPDATE  container.call_number_bucket
+				SET     owner = dest_usr, name = name || suffix
+				WHERE   id = bucket_row.id;
+			EXCEPTION WHEN unique_violation THEN
+				suffix := suffix || ' ';
+				CONTINUE;
+			END;
+			EXIT;
+		END LOOP;
+	END LOOP;
+
+	FOR bucket_row in
+		SELECT id, name
+		FROM   container.copy_bucket
+		WHERE  owner = src_usr
+	LOOP
+		suffix := ' (' || src_usr || ')';
+		LOOP
+			BEGIN
+				UPDATE  container.copy_bucket
+				SET     owner = dest_usr, name = name || suffix
+				WHERE   id = bucket_row.id;
+			EXCEPTION WHEN unique_violation THEN
+				suffix := suffix || ' ';
+				CONTINUE;
+			END;
+			EXIT;
+		END LOOP;
+	END LOOP;
+
+	FOR bucket_row in
+		SELECT id, name
+		FROM   container.user_bucket
+		WHERE  owner = src_usr
+	LOOP
+		suffix := ' (' || src_usr || ')';
+		LOOP
+			BEGIN
+				UPDATE  container.user_bucket
+				SET     owner = dest_usr, name = name || suffix
+				WHERE   id = bucket_row.id;
+			EXCEPTION WHEN unique_violation THEN
+				suffix := suffix || ' ';
+				CONTINUE;
+			END;
+			EXIT;
+		END LOOP;
+	END LOOP;
+
+	UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
+
+    -- vandelay.*
+	-- transfer queues the same way we transfer buckets (see above)
+	FOR queue_row in
+		SELECT id, name
+		FROM   vandelay.queue
+		WHERE  owner = src_usr
+	LOOP
+		suffix := ' (' || src_usr || ')';
+		LOOP
+			BEGIN
+				UPDATE  vandelay.queue
+				SET     owner = dest_usr, name = name || suffix
+				WHERE   id = queue_row.id;
+			EXCEPTION WHEN unique_violation THEN
+				suffix := suffix || ' ';
+				CONTINUE;
+			END;
+			EXIT;
+		END LOOP;
+	END LOOP;
+
+    -- money.*
+    PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
+    PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
+    UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
+    UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
+    UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
+
+    -- action.*
+    UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
+    UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
+    UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
+    UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
+
+    UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
+    UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
+    UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
+    UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
+
+    UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
+    UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
+    UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
+    UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
+    UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
+
+    -- acq.*
+    UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
+	UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
+
+	-- transfer picklists the same way we transfer buckets (see above)
+	FOR picklist_row in
+		SELECT id, name
+		FROM   acq.picklist
+		WHERE  owner = src_usr
+	LOOP
+		suffix := ' (' || src_usr || ')';
+		LOOP
+			BEGIN
+				UPDATE  acq.picklist
+				SET     owner = dest_usr, name = name || suffix
+				WHERE   id = picklist_row.id;
+			EXCEPTION WHEN unique_violation THEN
+				suffix := suffix || ' ';
+				CONTINUE;
+			END;
+			EXIT;
+		END LOOP;
+	END LOOP;
+
+    UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
+    UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
+    UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
+    UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
+    UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
+    UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
+    UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
+    UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
+
+    -- asset.*
+    UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
+    UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
+    UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
+    UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
+    UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
+    UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
+
+    -- serial.*
+    UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
+    UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
+
+    -- reporter.*
+    -- It's not uncommon to define the reporter schema in a replica 
+    -- DB only, so don't assume these tables exist in the write DB.
+    BEGIN
+    	UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
+    EXCEPTION WHEN undefined_table THEN
+        -- do nothing
+    END;
+    BEGIN
+    	UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
+    EXCEPTION WHEN undefined_table THEN
+        -- do nothing
+    END;
+    BEGIN
+    	UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
+    EXCEPTION WHEN undefined_table THEN
+        -- do nothing
+    END;
+    BEGIN
+		-- transfer folders the same way we transfer buckets (see above)
+		FOR folder_row in
+			SELECT id, name
+			FROM   reporter.template_folder
+			WHERE  owner = src_usr
+		LOOP
+			suffix := ' (' || src_usr || ')';
+			LOOP
+				BEGIN
+					UPDATE  reporter.template_folder
+					SET     owner = dest_usr, name = name || suffix
+					WHERE   id = folder_row.id;
+				EXCEPTION WHEN unique_violation THEN
+					suffix := suffix || ' ';
+					CONTINUE;
+				END;
+				EXIT;
+			END LOOP;
+		END LOOP;
+    EXCEPTION WHEN undefined_table THEN
+        -- do nothing
+    END;
+    BEGIN
+		-- transfer folders the same way we transfer buckets (see above)
+		FOR folder_row in
+			SELECT id, name
+			FROM   reporter.report_folder
+			WHERE  owner = src_usr
+		LOOP
+			suffix := ' (' || src_usr || ')';
+			LOOP
+				BEGIN
+					UPDATE  reporter.report_folder
+					SET     owner = dest_usr, name = name || suffix
+					WHERE   id = folder_row.id;
+				EXCEPTION WHEN unique_violation THEN
+					suffix := suffix || ' ';
+					CONTINUE;
+				END;
+				EXIT;
+			END LOOP;
+		END LOOP;
+    EXCEPTION WHEN undefined_table THEN
+        -- do nothing
+    END;
+    BEGIN
+		-- transfer folders the same way we transfer buckets (see above)
+		FOR folder_row in
+			SELECT id, name
+			FROM   reporter.output_folder
+			WHERE  owner = src_usr
+		LOOP
+			suffix := ' (' || src_usr || ')';
+			LOOP
+				BEGIN
+					UPDATE  reporter.output_folder
+					SET     owner = dest_usr, name = name || suffix
+					WHERE   id = folder_row.id;
+				EXCEPTION WHEN unique_violation THEN
+					suffix := suffix || ' ';
+					CONTINUE;
+				END;
+				EXIT;
+			END LOOP;
+		END LOOP;
+    EXCEPTION WHEN undefined_table THEN
+        -- do nothing
+    END;
+
+    -- propagate preferred name values from the source user to the
+    -- destination user, but only when values are not being replaced.
+    WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
+    UPDATE actor.usr SET 
+        pref_prefix = 
+            COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
+        pref_first_given_name = 
+            COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
+        pref_second_given_name = 
+            COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
+        pref_family_name = 
+            COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
+        pref_suffix = 
+            COALESCE(pref_suffix, (SELECT pref_suffix FROM susr)),
+        name_keywords =
+            COALESCE(name_keywords, '') || ' ' ||
+                COALESCE((SELECT name_keywords FROM susr), '')
+    WHERE id = dest_usr;
+
+    -- Finally, delete the source user
+    DELETE FROM actor.usr WHERE id = src_usr;
+
+END;
+$$ LANGUAGE plpgsql;
+
+
+COMMIT;
+
diff --git a/Open-ILS/src/templates/opac/myopac/prefs.tt2 b/Open-ILS/src/templates/opac/myopac/prefs.tt2
index f2462a5..f3b80db 100644
--- a/Open-ILS/src/templates/opac/myopac/prefs.tt2
+++ b/Open-ILS/src/templates/opac/myopac/prefs.tt2
@@ -15,9 +15,11 @@
 
                 <td class='light_border'>[% l(
                     HUMAN_NAME_FORMAT,
-                    ctx.user.prefix, ctx.user.first_given_name,
-                    ctx.user.second_given_name, ctx.user.family_name,
-                    ctx.user.suffix
+                    (ctx.user.pref_prefix || ctx.user.prefix), 
+                    (ctx.user.pref_first_given_name || ctx.user.first_given_name),
+                    (ctx.user.pref_second_given_name || ctx.user.second_given_name), 
+                    (ctx.user.pref_family_name || ctx.user.family_name),
+                    (ctx.user.pref_suffix || ctx.user.suffix)
                 ) | html %]</td>
 
                 <td></td>
diff --git a/Open-ILS/src/templates/opac/parts/topnav.tt2 b/Open-ILS/src/templates/opac/parts/topnav.tt2
index e9b58ff..f8dd9ed 100644
--- a/Open-ILS/src/templates/opac/parts/topnav.tt2
+++ b/Open-ILS/src/templates/opac/parts/topnav.tt2
@@ -19,7 +19,10 @@
         <div id="dash_wrapper">
             <div id="dash_identity">
                 <span id="dash_user">
-                    [%  l('[_1] [_2]', ctx.user.first_given_name, ctx.user.family_name) | html %]
+                    [%  l('[_1] [_2]', 
+                      (ctx.user.pref_first_given_name || ctx.user.first_given_name), 
+                      (ctx.user.pref_family_name || ctx.user.family_name)
+                    ) | html %]
                 </span>
                 <span class="dash_divider">|</span>
                 <span class="dash_account_buttons">
diff --git a/Open-ILS/src/templates/opac/register.tt2 b/Open-ILS/src/templates/opac/register.tt2
index 962fa9f..d108651 100644
--- a/Open-ILS/src/templates/opac/register.tt2
+++ b/Open-ILS/src/templates/opac/register.tt2
@@ -27,6 +27,9 @@ register_fields = [
     {class => 'stgu',  name = 'first_given_name', label => l('First Name')},
     {class => 'stgu',  name = 'second_given_name', label => l('Middle Name')},
     {class => 'stgu',  name = 'family_name', label => l('Last Name')},
+    {class => 'stgu',  name = 'pref_first_given_name', label => l('Preferred First Name')},
+    {class => 'stgu',  name = 'pref_second_given_name', label => l('Preferred Middle Name')},
+    {class => 'stgu',  name = 'pref_family_name', label => l('Preferred Last Name')},
     {class => 'stgma', name = 'street1', label => l('Street Address')},
     {class => 'stgma', name = 'street2', label => l('Street Address (2)')},
     {class => 'stgma', name = 'city', label => l('City')},
@@ -123,8 +126,16 @@ END;
 FOR field_def IN register_fields;
     fclass = field_def.class;
     fname = field_def.name;
+    orig_name = fname;
+
     field_path = fclass _ "." _ fname;
 
+    IF fname.match('^pref_');
+        # Preferred name fields adopt most visibility, etc.
+        # settings from the primary name counterparts.
+        fname = fname.remove('^pref_');
+    END;
+
     show = ctx.register.settings.$fclass.$fname.show;
     require = ctx.register.settings.$fclass.$fname.require;
     example = ctx.register.settings.$fclass.$fname.example;
@@ -133,6 +144,11 @@ FOR field_def IN register_fields;
     invalid_require = ctx.register.invalid.$fclass.$fname.require;
     invalid_regex = ctx.register.invalid.$fclass.$fname.regex;
 
+    IF orig_name.match('^pref_');
+        show = show || require;
+        require = 0; # pref name values never required
+    END;
+
     NEXT UNLESS require OR show;
 %]
 <tr>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
index bd58914..87a1522 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
@@ -214,53 +214,143 @@ within the "form" by name for validation.
   </div>
 </div>
 
-<!-- PREFIX -->
-
-<div class="row reg-field-row" ng-show="show_field('au.prefix')">
-  [% draw_field_label('au', 'prefix') %]
-  [% draw_form_input('au', 'prefix'); %]
-  <div class="col-md-6 patron-reg-example">
-    [% draw_example_text('au', 'prefix') %]
+<div class="row reg-field-row">
+  <div class="col-md-6">
+    <ul class="nav nav-pills nav-pills-like-tabs">
+      <li ng-class="{active : name_tab == 'primary'}">
+        <a ng-click="name_tab='primary'">[% l('Primary Name') %]</a>
+      </li>
+      <li ng-class="{active : name_tab == 'preferred'}">
+        <a ng-click="name_tab='preferred'">[% l('Preferred Name') %]</a>
+      </li>
+    </ul>
   </div>
 </div>
 
-<!-- FIRST_GIVEN_NAME -->
+<div ng-show="name_tab == 'primary'">
 
-<div class="row reg-field-row" ng-show="show_field('au.first_given_name')">
-  [% draw_field_label('au', 'first_given_name') %]
-  [% draw_form_input('au', 'first_given_name'); %]
-  <div class="col-md-6 patron-reg-example">
-    [% draw_example_text('au', 'first_given_name') %]
+  <!-- PREFIX -->
+
+  <div class="row reg-field-row" ng-show="show_field('au.prefix')">
+    [% draw_field_label('au', 'prefix') %]
+    [% draw_form_input('au', 'prefix'); %]
+    <div class="col-md-6 patron-reg-example">
+      [% draw_example_text('au', 'prefix') %]
+    </div>
   </div>
-</div>
 
-<!-- SECOND_GIVEN_NAME -->
+  <!-- FIRST_GIVEN_NAME -->
 
-<div class="row reg-field-row" ng-show="show_field('au.second_given_name')">
-  [% draw_field_label('au', 'second_given_name') %]
-  [% draw_form_input('au', 'second_given_name'); %]
-  <div class="col-md-6 patron-reg-example">
-    [% draw_example_text('au', 'second_given_name') %]
+  <div class="row reg-field-row" ng-show="show_field('au.first_given_name')">
+    [% draw_field_label('au', 'first_given_name') %]
+    [% draw_form_input('au', 'first_given_name'); %]
+    <div class="col-md-6 patron-reg-example">
+      [% draw_example_text('au', 'first_given_name') %]
+    </div>
   </div>
-</div>
 
-<!-- FAMILY_NAME -->
+  <!-- SECOND_GIVEN_NAME -->
 
-<div class="row reg-field-row" ng-show="show_field('au.family_name')">
-  [% draw_field_label('au', 'family_name') %]
-  [% draw_form_input('au', 'family_name'); %]
-  <div class="col-md-6 patron-reg-example">
-    [% draw_example_text('au', 'family_name') %]
+  <div class="row reg-field-row" ng-show="show_field('au.second_given_name')">
+    [% draw_field_label('au', 'second_given_name') %]
+    [% draw_form_input('au', 'second_given_name'); %]
+    <div class="col-md-6 patron-reg-example">
+      [% draw_example_text('au', 'second_given_name') %]
+    </div>
+  </div>
+
+  <!-- FAMILY_NAME -->
+
+  <div class="row reg-field-row" ng-show="show_field('au.family_name')">
+    [% draw_field_label('au', 'family_name') %]
+    [% draw_form_input('au', 'family_name'); %]
+    <div class="col-md-6 patron-reg-example">
+      [% draw_example_text('au', 'family_name') %]
+    </div>
+  </div>
+
+  <!-- SUFFIX -->
+
+  <div class="row reg-field-row" ng-show="show_field('au.suffix')">
+    [% draw_field_label('au', 'suffix') %]
+    [% draw_form_input('au', 'suffix'); %]
+    <div class="col-md-6 patron-reg-example">
+      [% draw_example_text('au', 'suffix') %]
+    </div>
+  </div>
+</div> <!-- ng-show == primary -->
+
+<div ng-show="name_tab == 'preferred'" class="patron-reg-pref-names">
+
+  <!-- PREFIX -->
+
+  <div class="row reg-field-row" ng-show="show_field('au.pref_prefix')">
+    [% draw_field_label('au', 'pref_prefix') %]
+    [% draw_form_input('au', 'pref_prefix'); %]
+    <div class="col-md-6 patron-reg-example">
+      [% draw_example_text('au', 'pref_prefix') %]
+    </div>
+  </div>
+
+  <!-- FIRST_GIVEN_NAME -->
+
+  <div class="row reg-field-row" ng-show="show_field('au.pref_first_given_name')">
+    [% draw_field_label('au', 'pref_first_given_name') %]
+    [% draw_form_input('au', 'pref_first_given_name'); %]
+    <div class="col-md-6 patron-reg-example">
+      [% draw_example_text('au', 'pref_first_given_name') %]
+    </div>
+  </div>
+
+  <!-- SECOND_GIVEN_NAME -->
+
+  <div class="row reg-field-row" ng-show="show_field('au.pref_second_given_name')">
+    [% draw_field_label('au', 'pref_second_given_name') %]
+    [% draw_form_input('au', 'pref_second_given_name'); %]
+    <div class="col-md-6 patron-reg-example">
+      [% draw_example_text('au', 'pref_second_given_name') %]
+    </div>
+  </div>
+
+  <!-- FAMILY_NAME -->
+
+  <div class="row reg-field-row" ng-show="show_field('au.pref_family_name')">
+    [% draw_field_label('au', 'pref_family_name') %]
+    [% draw_form_input('au', 'pref_family_name'); %]
+    <div class="col-md-6 patron-reg-example">
+      [% draw_example_text('au', 'pref_family_name') %]
+    </div>
   </div>
-</div>
 
-<!-- SUFFIX -->
+  <!-- SUFFIX -->
+
+  <div class="row reg-field-row" ng-show="show_field('au.pref_suffix')">
+    [% draw_field_label('au', 'pref_suffix') %]
+    [% draw_form_input('au', 'pref_suffix'); %]
+    <div class="col-md-6 patron-reg-example">
+      [% draw_example_text('au', 'pref_suffix') %]
+    </div>
+  </div>
+</div> <!-- ng-show == preferred -->
 
-<div class="row reg-field-row" ng-show="show_field('au.suffix')">
-  [% draw_field_label('au', 'suffix') %]
-  [% draw_form_input('au', 'suffix'); %]
+<!-- indicate bottom of name tabs -->
+<div class="row reg-field-row">
+  <div class="col-md-6"><hr class="patron-reg-names-separator"/></div>
+</div>
+
+<div class="row reg-field-row" ng-show="show_field('au.name_keywords')">
+  [% draw_field_label('au', 'name_keywords') %]
+  <div class="col-md-3 reg-field-input">
+    <textarea 
+      class="form-control" 
+      ng-model="patron.name_keywords"
+      ng-pattern="field_pattern('au', 'name_keywords')"
+      ng-change="field_modified()" 
+      ng-blur="handle_field_changed(patron, 'name_keywords')">
+    </textarea>
+  </div>
   <div class="col-md-6 patron-reg-example">
-    [% draw_example_text('au', 'suffix') %]
+    [% draw_example_text('au', 'name_keywords') %]
   </div>
 </div>
 
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
index f663db9..cf0bc4e 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
@@ -1,6 +1,21 @@
 
 <div ng-cloak class="patron-summary-grid-wrapper">
   <div ng-show="patron()" id="patron-summary-grid">
+    <div class="row patron-summary-pref-name"
+      ng-if="patron().pref_family_name() || patron().pref_first_given_name() || patron().second_given_name()">
+      <div class="col-md-12">
+        [% l('[_1], [_2] [_3] (Preferred)', 
+          '{{patron().pref_family_name() || patron().family_name()}}',
+          '{{patron().pref_first_given_name() || patron().first_given_name()}}',
+          '{{patron().pref_second_given_name() || patron().second_given_name()}}') 
+        %]
+      </div>
+    </div>
+    <div ng-if="patron().name_keywords()" class="row patron-summary-pref-name">
+      <div class="col-md-12">
+        <a title="{{patron().name_keywords()}}">[% l('Name Keywords') %]</a>
+      </div>
+    </div>
     <div class="row" 
       ng-class="{'patron-summary-divider' : !$index}"
       ng-repeat="penalty in alert_penalties()">
@@ -159,6 +174,10 @@
       <div class="col-md-5">{{map.stat_cat().name()}}</div>
       <div class="col-md-7">{{map.stat_cat_entry()}}</div>
     </div>
+    <div class="row" ng-if="patron().name_keywords()">
+      <div class="col-md-5">[% l('Name Keywords') %]</div>
+      <div class="col-md-7">{{patron().name_keywords()}}</div>
+    </div>
   </div>
 
   <div class="row" ng-repeat="addr in patron().addresses()">
diff --git a/Open-ILS/src/templates/staff/css/circ.css.tt2 b/Open-ILS/src/templates/staff/css/circ.css.tt2
index eb7b139..f0d29c2 100644
--- a/Open-ILS/src/templates/staff/css/circ.css.tt2
+++ b/Open-ILS/src/templates/staff/css/circ.css.tt2
@@ -17,6 +17,11 @@ but the ones I'm finding aren't quite cutting it..*/
 .patron-summary-act-link {font-size: .8em;}
 .patron-summary-has-notes:hover, .patron-summary-has-notes:visited{ text-decoration: none; }
 
+.patron-summary-pref-name {
+  padding-left: 6px;
+  color: #5cb85c;
+}
+
 /* FIXME: use .barcode instead */
 #patron-checkout-barcode,
 #patron-renewal-barcode,
@@ -168,6 +173,15 @@ but the ones I'm finding aren't quite cutting it..*/
   color: red;
 }
 
+.patron-reg-names-separator {
+  margin-top: 3px;
+  margin-bottom: 3px;
+}
+
+.patron-reg-pref-names {
+  background-color: rgb(215, 215, 215);
+}
+
 /* -- end patron registration -- */
 
 [%# 
diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_checkout.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_checkout.tt2
index 9ba5c31..646c383 100644
--- a/Open-ILS/src/templates/staff/share/print_templates/t_checkout.tt2
+++ b/Open-ILS/src/templates/staff/share/print_templates/t_checkout.tt2
@@ -3,10 +3,16 @@ Template for printing checkout receipts; fields available include:
 
 * patron - has several fields from the patron object, including a financial summary
 
+  * prefix
   * first_given_name
   * second_given_name
   * family_name
   * suffix
+  * pref_prefix
+  * pref_first_given_name
+  * pref_second_given_name
+  * pref_family_name
+  * pref_suffix
   * card.barcode
   * money_summary.balance_owed - current balance
   * money_summary.total_paid - payments made on outstanding fines/fees
diff --git a/Open-ILS/src/templates/staff/share/t_patron_search_form.tt2 b/Open-ILS/src/templates/staff/share/t_patron_search_form.tt2
index 9eab22e..e9de365 100644
--- a/Open-ILS/src/templates/staff/share/t_patron_search_form.tt2
+++ b/Open-ILS/src/templates/staff/share/t_patron_search_form.tt2
@@ -26,15 +26,16 @@
             ng-model="searchArgs.second_given_name" placeholder="[% l('Middle Name') %]"/>
         </div>
 
-        <div class="col-md-2" ng-mouseover="setLastFormElement()">
-          <input type="submit" class="btn btn-primary" value="[% l('Search') %]"/>
+        <div class="col-md-2">
+            <input type="text" class="form-control" ng-model="searchArgs.name"
+            placeholder="[% l('Name Keywords') %]" title="[% l('Name Keywords') %]"/>
         </div>
 
         <div class="col-md-2" ng-mouseover="setLastFormElement()">
-          <input type="reset" class="btn btn-primary" ng-click="clearForm()" 
-            value="[% l('Clear Form') %]"/>
+          <input type="submit" class="btn btn-primary" value="[% l('Search') %]"/>
         </div>
 
+
         <div class="col-md-2">
           <button class="btn btn-default" ng-click="applyShowExtras($event, true)" 
             ng-mouseover="setLastFormElement()"
@@ -66,17 +67,18 @@
           <input type="text" class="form-control" 
             ng-model="searchArgs.email" placeholder="[% l('Email') %]"/>
         </div>
-        <div class="col-md-2">
-          <input type="text" class="form-control" 
-            ng-model="searchArgs.ident" placeholder="[% l('Identification') %]"/>
+        <div class="col-md-2" ng-mouseover="setLastFormElement()">
+          <input type="reset" class="btn btn-primary" ng-click="clearForm()" 
+            value="[% l('Clear Form') %]"/>
         </div>
       </div>
 
       <div class="form-group" ng-show="showExtras">
         <div class="col-md-2">
           <input type="text" class="form-control" 
-            ng-model="searchArgs.id" placeholder="[% l('Database ID') %]"/>
+            ng-model="searchArgs.ident" placeholder="[% l('Identification') %]"/>
         </div>
+
         <div class="col-md-2">
           <input type="text" class="form-control" 
             ng-model="searchArgs.phone" placeholder="[% l('Phone') %]"/>
@@ -158,6 +160,10 @@
             <input type="text" class="form-control" ng-model="searchArgs.dob_day"
             placeholder="[% l('DOB Day') %]" title="[% l('DOB Day') %]"/>
         </div>
+        <div class="col-md-2">
+          <input type="text" class="form-control" 
+            ng-model="searchArgs.id" placeholder="[% l('Database ID') %]"/>
+        </div>
       </div>
     </form>
   </div>
diff --git a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
index 9af1c00..d863844 100644
--- a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
+++ b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
@@ -360,6 +360,9 @@ function($scope , $q , egCore , ngToast) {
         second_given_name : 'Martin',
         family_name : 'Jones',
         suffix : 'III',
+        pref_first_given_name : 'Martin',
+        pref_second_given_name : 'Joe',
+        pref_family_name : 'Smith',
         card : {
             barcode : '30393830393'
         },
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js b/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js
index cdb944c..1cbb05e 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js
@@ -321,6 +321,11 @@ function($scope , $q , $routeParams , egCore , egUser , patronSvc ,
             second_given_name : cusr.second_given_name(),
             family_name : cusr.family_name(),
             suffix : cusr.suffix(),
+            pref_prefix : cusr.pref_prefix(),
+            pref_first_given_name : cusr.pref_first_given_name(),
+            pref_secondg_given_name : cusr.second_given_name(),
+            pref_family_name : cusr.pref_family_name(),
+            suffix : cusr.suffix(),
             card : { barcode : cusr.card().barcode() },
             money_summary : patronSvc.patron_stats.fines,
             expire_date : cusr.expire_date(),
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
index 6a2d092..9f6dbeb 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
@@ -1236,6 +1236,8 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
     // has at the currently selected patron home org unit.
     $scope.perms = {};
 
+    $scope.name_tab = 'primary';
+
     if (!$scope.edit_passthru) {
         // in edit more, scope.edit_passthru is delivered to us by
         // the enclosing controller.  In register mode, there is 
@@ -1407,6 +1409,8 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         'au.passwd' :  3,
         'au.first_given_name' : 3,
         'au.family_name' : 3,
+        'au.pref_first_given_name' : 2,
+        'au.pref_family_name' : 2,
         'au.ident_type' : 3,
         'au.ident_type2' : 2,
         'au.home_ou' : 3,
@@ -1424,7 +1428,8 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         'aua.valid' : 2,
         'aua.within_city_limits' : 2,
         'stat_cats' : 1,
-        'surveys' : 1
+        'surveys' : 1,
+        'au.name_keywords': 1
     }; 
 
     // Returns true if the selected field should be visible
@@ -1438,12 +1443,26 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         if (field_visibility[field_key] == undefined) {
             // compile and cache the visibility for the selected field
 
-            var req_set = 'ui.patron.edit.' + field_key + '.require';
-            var sho_set = 'ui.patron.edit.' + field_key + '.show';
-            var sug_set = 'ui.patron.edit.' + field_key + '.suggest';
+            // The preferred name fields use the primary name field settings
+            var org_key = field_key;
+            var alt_name = false;
+            if (field_key.match(/^au.alt_/)) {
+                alt_name = true;
+                org_key = field_key.slice(7);
+            }
+
+            var req_set = 'ui.patron.edit.' + org_key + '.require';
+            var sho_set = 'ui.patron.edit.' + org_key + '.show';
+            var sug_set = 'ui.patron.edit.' + org_key + '.suggest';
 
             if ($scope.org_settings[req_set]) {
-                field_visibility[field_key] = 3;
+                if (alt_name) {
+                    // Avoid requiring alt name fields when primary 
+                    // name fields are required.
+                    field_visibility[field_key] = 2;
+                } else {
+                    field_visibility[field_key] = 3;
+                }
 
             } else if ($scope.org_settings[sho_set]) {
                 field_visibility[field_key] = 2;
diff --git a/Open-ILS/web/js/ui/default/staff/services/patron_search.js b/Open-ILS/web/js/ui/default/staff/services/patron_search.js
index b0f1bed..07e961e 100644
--- a/Open-ILS/web/js/ui/default/staff/services/patron_search.js
+++ b/Open-ILS/web/js/ui/default/staff/services/patron_search.js
@@ -780,6 +780,8 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egCore,
                 search.home_ou = args.home_ou.id(); // passed separately
             } else if (key == 'inactive') {
                 search.inactive = val;
+            } else if (key == 'name') { // name keywords search
+                search.name = {value: val};
             } else {
                 search[key] = {value : val, group : 0};
             }

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |    9 +
 .../OpenILS/Application/Storage/Publisher/actor.pm |   66 +++++++--
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    2 +-
 Open-ILS/src/sql/Pg/005.schema.actors.sql          |   45 ++++++
 Open-ILS/src/sql/Pg/015.schema.staging.sql         |    3 +
 Open-ILS/src/sql/Pg/999.functions.global.sql       |   33 ++++
 .../1122.schema.patron-alt-name.sql}               |  118 ++++++++++++++-
 Open-ILS/src/templates/opac/myopac/prefs.tt2       |    8 +-
 Open-ILS/src/templates/opac/parts/topnav.tt2       |    5 +-
 Open-ILS/src/templates/opac/register.tt2           |   16 ++
 Open-ILS/src/templates/staff/circ/patron/index.tt2 |    1 +
 .../src/templates/staff/circ/patron/t_edit.tt2     |  156 +++++++++++++++----
 .../src/templates/staff/circ/patron/t_summary.tt2  |   14 ++
 Open-ILS/src/templates/staff/css/circ.css.tt2      |   21 +++
 .../staff/share/print_templates/t_bill_payment.tt2 |    1 +
 .../share/print_templates/t_bills_current.tt2      |    1 +
 .../share/print_templates/t_bills_historical.tt2   |    1 +
 .../staff/share/print_templates/t_checkout.tt2     |    6 +
 .../staff/share/print_templates/t_items_out.tt2    |    1 +
 .../templates/staff/share/t_patron_search_form.tt2 |   22 ++-
 .../js/ui/default/staff/admin/workstation/app.js   |    3 +
 .../web/js/ui/default/staff/circ/patron/bills.js   |   15 ++
 .../js/ui/default/staff/circ/patron/checkout.js    |    5 +
 .../js/ui/default/staff/circ/patron/items_out.js   |    5 +
 .../web/js/ui/default/staff/circ/patron/regctl.js  |   29 +++-
 .../js/ui/default/staff/services/patron_search.js  |    2 +
 .../Circulation/patron-pref-name.adoc              |   36 +++++
 27 files changed, 550 insertions(+), 74 deletions(-)
 copy Open-ILS/src/sql/Pg/{version-upgrade/2.12.5-2.12.6-upgrade-db.sql => upgrade/1122.schema.patron-alt-name.sql} (66%)
 create mode 100644 docs/RELEASE_NOTES_NEXT/Circulation/patron-pref-name.adoc


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list