LP#1776020 Patron preferred name & name keywords
authorBill Erickson <berickxx@gmail.com>
Mon, 21 May 2018 02:15:23 +0000 (22:15 -0400)
committerGalen Charlton <gmc@equinoxinitiative.org>
Tue, 4 Sep 2018 17:10:56 +0000 (13:10 -0400)
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@gmail.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
18 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/actor.pm
Open-ILS/src/sql/Pg/005.schema.actors.sql
Open-ILS/src/sql/Pg/015.schema.staging.sql
Open-ILS/src/sql/Pg/999.functions.global.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql [new file with mode: 0644]
Open-ILS/src/templates/opac/myopac/prefs.tt2
Open-ILS/src/templates/opac/parts/topnav.tt2
Open-ILS/src/templates/opac/register.tt2
Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
Open-ILS/src/templates/staff/css/circ.css.tt2
Open-ILS/src/templates/staff/share/print_templates/t_checkout.tt2
Open-ILS/src/templates/staff/share/t_patron_search_form.tt2
Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js
Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
Open-ILS/web/js/ui/default/staff/services/patron_search.js

index e60c5de..9436ceb 100644 (file)
@@ -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"/>
index 1cef88b..686d960 100644 (file)
@@ -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 = '';
index 1152d97..61d5bae 100644 (file)
@@ -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,
index 4dfc3ee..ddd3b71 100644 (file)
@@ -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,
index 521e965..927e47c 100644 (file)
@@ -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 (file)
index 0000000..6eea68e
--- /dev/null
@@ -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;
+
index f2462a5..f3b80db 100644 (file)
 
                 <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>
index e9b58ff..f8dd9ed 100644 (file)
         <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">
index 962fa9f..d108651 100644 (file)
@@ -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>
index bd58914..87a1522 100644 (file)
@@ -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>
 
index f663db9..cf0bc4e 100644 (file)
@@ -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()">
       <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()">
index eb7b139..f0d29c2 100644 (file)
@@ -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 -- */
 
 [%# 
index 9ba5c31..646c383 100644 (file)
@@ -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
index 9eab22e..e9de365 100644 (file)
             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()"
           <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') %]"/>
             <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>
index 9af1c00..d863844 100644 (file)
@@ -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'
         },
index cdb944c..1cbb05e 100644 (file)
@@ -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(),
index 6a2d092..9f6dbeb 100644 (file)
@@ -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;
index b0f1bed..07e961e 100644 (file)
@@ -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};
             }