From 8d0f5b6f4e65a6c77f2f943b29fc2642ac85dcab Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Sun, 20 May 2018 22:15:23 -0400 Subject: [PATCH] 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 Signed-off-by: Kathy Lussier Signed-off-by: Galen Charlton --- Open-ILS/examples/fm_IDL.xml | 9 + .../Application/Storage/Publisher/actor.pm | 66 ++- 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 | 21 + .../upgrade/XXXX.schema.patron-alt-name.sql | 422 ++++++++++++++++++ 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 + .../templates/staff/circ/patron/t_edit.tt2 | 156 +++++-- .../templates/staff/circ/patron/t_summary.tt2 | 19 + Open-ILS/src/templates/staff/css/circ.css.tt2 | 14 + .../share/print_templates/t_checkout.tt2 | 6 + .../staff/share/t_patron_search_form.tt2 | 22 +- .../ui/default/staff/admin/workstation/app.js | 3 + .../ui/default/staff/circ/patron/checkout.js | 5 + .../js/ui/default/staff/circ/patron/regctl.js | 29 +- .../default/staff/services/patron_search.js | 2 + 18 files changed, 785 insertions(+), 66 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.schema.patron-alt-name.sql diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index e60c5dea1c..9436ceb14d 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 + + + + + + @@ -10382,6 +10388,9 @@ SELECT usr, + + + 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 1cef88bfd6..686d960ebf 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 1152d97616..61d5baed27 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 4dfc3ee98a..ddd3b71b10 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 521e96585e..927e47c137 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 0000000000..6eea68e639 --- /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 f2462a5d6b..f3b80dbae8 100644 --- a/Open-ILS/src/templates/opac/myopac/prefs.tt2 +++ b/Open-ILS/src/templates/opac/myopac/prefs.tt2 @@ -15,9 +15,11 @@ [% 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 %] diff --git a/Open-ILS/src/templates/opac/parts/topnav.tt2 b/Open-ILS/src/templates/opac/parts/topnav.tt2 index e9b58ffb1d..f8dd9edc8b 100644 --- a/Open-ILS/src/templates/opac/parts/topnav.tt2 +++ b/Open-ILS/src/templates/opac/parts/topnav.tt2 @@ -19,7 +19,10 @@
- [% 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 %] |
- - -
- [% draw_field_label('au', 'prefix') %] - [% draw_form_input('au', 'prefix'); %] -
- [% draw_example_text('au', 'prefix') %] + - +
-
- [% draw_field_label('au', 'first_given_name') %] - [% draw_form_input('au', 'first_given_name'); %] -
- [% draw_example_text('au', 'first_given_name') %] + + +
+ [% draw_field_label('au', 'prefix') %] + [% draw_form_input('au', 'prefix'); %] +
+ [% draw_example_text('au', 'prefix') %] +
-
- + -
- [% draw_field_label('au', 'second_given_name') %] - [% draw_form_input('au', 'second_given_name'); %] -
- [% draw_example_text('au', 'second_given_name') %] +
+ [% draw_field_label('au', 'first_given_name') %] + [% draw_form_input('au', 'first_given_name'); %] +
+ [% draw_example_text('au', 'first_given_name') %] +
-
- + -
- [% draw_field_label('au', 'family_name') %] - [% draw_form_input('au', 'family_name'); %] -
- [% draw_example_text('au', 'family_name') %] +
+ [% draw_field_label('au', 'second_given_name') %] + [% draw_form_input('au', 'second_given_name'); %] +
+ [% draw_example_text('au', 'second_given_name') %] +
+
+ + + +
+ [% draw_field_label('au', 'family_name') %] + [% draw_form_input('au', 'family_name'); %] +
+ [% draw_example_text('au', 'family_name') %] +
+
+ + + +
+ [% draw_field_label('au', 'suffix') %] + [% draw_form_input('au', 'suffix'); %] +
+ [% draw_example_text('au', 'suffix') %] +
+
+
+ +
+ + + +
+ [% draw_field_label('au', 'pref_prefix') %] + [% draw_form_input('au', 'pref_prefix'); %] +
+ [% draw_example_text('au', 'pref_prefix') %] +
+
+ + + +
+ [% draw_field_label('au', 'pref_first_given_name') %] + [% draw_form_input('au', 'pref_first_given_name'); %] +
+ [% draw_example_text('au', 'pref_first_given_name') %] +
+
+ + + +
+ [% draw_field_label('au', 'pref_second_given_name') %] + [% draw_form_input('au', 'pref_second_given_name'); %] +
+ [% draw_example_text('au', 'pref_second_given_name') %] +
+
+ + + +
+ [% draw_field_label('au', 'pref_family_name') %] + [% draw_form_input('au', 'pref_family_name'); %] +
+ [% draw_example_text('au', 'pref_family_name') %] +
-
- + + +
+ [% draw_field_label('au', 'pref_suffix') %] + [% draw_form_input('au', 'pref_suffix'); %] +
+ [% draw_example_text('au', 'pref_suffix') %] +
+
+
-
- [% draw_field_label('au', 'suffix') %] - [% draw_form_input('au', 'suffix'); %] + +
+

+
+ +
+ [% draw_field_label('au', 'name_keywords') %] +
+ +
- [% draw_example_text('au', 'suffix') %] + [% draw_example_text('au', 'name_keywords') %]
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 f663db96e5..cf0bc4eb91 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 @@
+
+
+ [% 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()}}') + %] +
+
+
@@ -159,6 +174,10 @@
{{map.stat_cat().name()}}
{{map.stat_cat_entry()}}
+
+
[% l('Name Keywords') %]
+
{{patron().name_keywords()}}
+
diff --git a/Open-ILS/src/templates/staff/css/circ.css.tt2 b/Open-ILS/src/templates/staff/css/circ.css.tt2 index eb7b139b80..f0d29c28f4 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 9ba5c31754..646c383c16 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 9eab22e208..e9de36573a 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') %]"/>
-
- +
+
- +
+
-
- +
+
+ ng-model="searchArgs.ident" placeholder="[% l('Identification') %]"/>
+
@@ -158,6 +160,10 @@
+
+ +
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 9af1c00dea..d863844e3d 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 cdb944c97e..1cbb05e220 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 6a2d092589..9f6dbeb355 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 b0f1bede16..07e961eefa 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}; } -- 2.43.2