From b9e531a162bd7582cd8daf50eb218b60398fbc98 Mon Sep 17 00:00:00 2001 From: Lebbeous Fogle-Weekley Date: Tue, 27 Dec 2011 18:02:38 -0500 Subject: [PATCH] Mark email addresses and phone numbers invalid This comes from an idea from Jeff Godin. For staff to indicate an email address or phone number "invalid," for whatever definition of "invalid" has meaning at the institution (as in invalid addresses today), there is now a UI control in the patron editor. This UI control invokes a middle layer method that will clear the email (or phone) field from actor.usr, and create a corresponding standing penalty against the user, for staff to notice next time they bring up said patron in the staff client. Such penalties will be archived whenever that patron's email address or phone number is updated again. The middle layer method is a wrapper around a method that may take different parameters (like an email address instead of a patron ID) to potentially support future uses such as invalidating email addresses automatically in batch. Signed-off-by: Lebbeous Fogle-Weekley Signed-off-by: Bill Erickson --- .../src/perlmods/lib/OpenILS/Application/Actor.pm | 157 ++++++++++++++++++++- .../src/perlmods/lib/OpenILS/Utils/BadContact.pm | 115 +++++++++++++++ Open-ILS/src/sql/Pg/002.schema.config.sql | 46 ++++++ .../XXXX.data.mark-email-and-phone-invalid.sql | 59 ++++++++ Open-ILS/src/templates/actor/user/register.tt2 | 1 + Open-ILS/web/js/dojo/openils/Util.js | 2 +- Open-ILS/web/js/dojo/openils/actor/nls/register.js | 3 +- Open-ILS/web/js/ui/default/actor/user/register.js | 62 ++++++++ Open-ILS/web/opac/locale/en-US/lang.dtd | 2 + .../server/patron/display_horiz_overlay.xul | 2 + .../staff_client/server/patron/display_overlay.xul | 2 + .../server/patron/standing_penalties.js | 6 + Open-ILS/xul/staff_client/server/patron/util.js | 12 +- .../staff_client/server/skin/patron_display.css | 5 + 14 files changed, 469 insertions(+), 5 deletions(-) create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/Utils/BadContact.pm create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.data.mark-email-and-phone-invalid.sql diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm index c6467697e4..b1ec6d811b 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm @@ -34,6 +34,7 @@ use OpenILS::Application::Actor::Stage; use OpenILS::Utils::CStoreEditor qw/:funcs/; use OpenILS::Utils::Penalty; +use OpenILS::Utils::BadContact; use List::Util qw/max reduce/; use UUID::Tiny qw/:std/; @@ -365,10 +366,25 @@ sub update_patron { if(ref($patron->mailing_address)); # create/update the patron first so we can use his id + + # $patron is the obj from the client (new data) and $new_patron is the + # patron object properly built for db insertion, so we need a third variable + # if we want to represent the old patron. + + my $old_patron; + if($patron->isnew()) { ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj); return $evt if $evt; - } else { $new_patron = $patron; } + } else { + $new_patron = $patron; + + # Did auth checking above already. + my $e = new_editor; + $old_patron = $e->retrieve_actor_user($patron->id) or + return $e->die_event; + $e->disconnect; + } ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj); return $evt if $evt; @@ -385,6 +401,9 @@ sub update_patron { return $evt if $evt; } + ( $new_patron, $evt ) = _clear_badcontact_penalties($session, $old_patron, $new_patron, $user_obj); + return $evt if $evt; + ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj); return $evt if $evt; @@ -877,6 +896,60 @@ sub _add_survey_responses { return ( $new_patron, undef ); } +sub _clear_badcontact_penalties { + my ($session, $old_patron, $new_patron, $user_obj) = @_; + + return ($new_patron, undef) unless $old_patron; + + my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP; + my $e = new_editor(xact => 1); + + # This ignores whether the caller of update_patron has any permission + # to remove penalties, but these penalties no longer make sense + # if an email address field (for example) is changed (and the caller must + # have perms to do *that*) so there's no reason not to clear the penalties. + + my $bad_contact_penalties = $e->search_actor_user_standing_penalty([ + { + "+csp" => {"name" => [values(%$PNM)]}, + "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id} + }, { + "join" => {"csp" => {}}, + "flesh" => 1, + "flesh_fields" => {"ausp" => ["standing_penalty"]} + } + ]) or return (undef, $e->die_event); + + return ($new_patron, undef) unless @$bad_contact_penalties; + + my @penalties_to_clear; + my ($field, $penalty_name); + + # For each field that might have an associated bad contact penalty, + # check for such penalties and add them to the to-clear list if that + # field has changed. + while (($field, $penalty_name) = each(%$PNM)) { + if ($old_patron->$field ne $new_patron->$field) { + push @penalties_to_clear, grep { + $_->standing_penalty->name eq $penalty_name + } @$bad_contact_penalties; + } + } + + foreach (@penalties_to_clear) { + # Note that this "archives" penalties, in the terminology of the staff + # client, instead of just deleting them. This may assist reporting, + # or preserving old contact information when it is still potentially + # of interest. + $_->standing_penalty($_->standing_penalty->id); # deflesh + $_->stop_date('now'); + $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event); + } + + $e->commit; + return ($new_patron, undef); +} + sub _create_stat_maps { @@ -4415,4 +4488,86 @@ sub address_alert_test { ]; } +__PACKAGE__->register_method( + method => "mark_users_contact_invalid", + api_name => "open-ils.actor.invalidate.email", + signature => { + desc => "Given a patron, clear the email field and put the old email address into a note and/or create a standing penalty, depending on OU settings", + params => [ + {desc => "Authentication token", type => "string"}, + {desc => "Patron ID", type => "number"}, + {desc => "Additional note text (optional)", type => "string"}, + {desc => "penalty org unit ID (optional)", type => "number"} + ], + return => {desc => "Event describing success or failure", type => "object"} + } +); + +__PACKAGE__->register_method( + method => "mark_users_contact_invalid", + api_name => "open-ils.actor.invalidate.day_phone", + signature => { + desc => "Given a patron, clear the day_phone field and put the old day_phone into a note and/or create a standing penalty, depending on OU settings", + params => [ + {desc => "Authentication token", type => "string"}, + {desc => "Patron ID", type => "number"}, + {desc => "Additional note text (optional)", type => "string"}, + {desc => "penalty org unit ID (optional)", type => "number"} + ], + return => {desc => "Event describing success or failure", type => "object"} + } +); + +__PACKAGE__->register_method( + method => "mark_users_contact_invalid", + api_name => "open-ils.actor.invalidate.evening_phone", + signature => { + desc => "Given a patron, clear the evening_phone field and put the old evening_phone into a note and/or create a standing penalty, depending on OU settings", + params => [ + {desc => "Authentication token", type => "string"}, + {desc => "Patron ID", type => "number"}, + {desc => "Additional note text (optional)", type => "string"}, + {desc => "penalty org unit ID (optional)", type => "number"} + ], + return => {desc => "Event describing success or failure", type => "object"} + } +); + +__PACKAGE__->register_method( + method => "mark_users_contact_invalid", + api_name => "open-ils.actor.invalidate.other_phone", + signature => { + desc => "Given a patron, clear the other_phone field and put the old other_phone into a note and/or create a standing penalty, depending on OU settings", + params => [ + {desc => "Authentication token", type => "string"}, + {desc => "Patron ID", type => "number"}, + {desc => "Additional note text (optional)", type => "string"}, + {desc => "penalty org unit ID (optional, default to top of org tree)", + type => "number"} + ], + return => {desc => "Event describing success or failure", type => "object"} + } +); + +sub mark_users_contact_invalid { + my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_; + + # This method invalidates an email address or a phone_number which + # removes the bad email address or phone number, copying its contents + # to a patron note, and institutes a standing penalty for "bad email" + # or "bad phone number" which is cleared when the user is saved or + # optionally only when the user is saved with an email address or + # phone number (or staff manually delete the penalty). + + my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0]; + + my $e = new_editor(authtoken => $auth, xact => 1); + return $e->die_event unless $e->checkauth; + + return OpenILS::Utils::BadContact->mark_users_contact_invalid( + $e, $contact_type, {usr => $patron_id}, + $addl_note, $penalty_ou, $e->requestor->id + ); +} + 1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/BadContact.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/BadContact.pm new file mode 100644 index 0000000000..f1784c1c20 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/BadContact.pm @@ -0,0 +1,115 @@ +package OpenILS::Utils::BadContact; + +use warnings; +use strict; + +use OpenILS::Event; +use OpenILS::Utils::CStoreEditor; +use OpenILS::Utils::Fieldmapper; +use OpenILS::Application::AppUtils; + +my $U = "OpenILS::Application::AppUtils"; + +our $PENALTY_NAME_MAP = { + email => "INVALID_PATRON_EMAIL_ADDRESS", + day_phone => "INVALID_PATRON_DAY_PHONE", + evening_phone => "INVALID_PATRON_EVENING_PHONE", + other_phone => "INVALID_PATRON_OTHER_PHONE" +}; + +sub mark_users_contact_invalid { + my ( + $class, $editor, $contact_type, $howfind, + $addl_note, $penalty_ou, $staff_id + ) = @_; + + if (not ref $howfind eq "HASH") { + return new OpenILS::Event( + "BAD_PARAMS", note => "howfind argument must be hash" + ); + } + + if (not exists $PENALTY_NAME_MAP->{$contact_type}) { + return new OpenILS::Event( + "BAD_PARAMS", note => "contact_type argument invalid" + ); + } + + my $penalty_name = $PENALTY_NAME_MAP->{$contact_type}; + + # we can find either user-by-id, or user(s)-by-contact-info + my $users; + + if (exists $howfind->{usr}) { + # just the specified patron + + $users = $editor->search_actor_user({ + id => $howfind->{usr}, deleted => "f" + }) or return $editor->die_event; + + } elsif (exists $howfind->{$contact_type}) { + # all users with matching contact data + + $users = $editor->search_actor_user({ + $contact_type => $howfind->{$contact_type}, deleted => "f" + }) or return $editor->die_event; + } + + # waste no more time if no users collected + return $editor->die_event(new OpenILS::Event("ACTOR_USER_NOT_FOUND")) + unless $users and @$users; + + # we'll need this to apply user penalties + my $penalty = $editor->search_config_standing_penalty({ + name => $penalty_name + }); + + if (not $penalty_ou) { + # Fallback to using top of org tree if no penalty_ou provided. This + # possibly makes sense in most cases anyway. + + my $results = $editor->json_query({ + "select" => {"aou" => ["id"]}, + "from" => {"aou" => {}}, + "where" => {"parent_ou" => undef} + }) or return $editor->die_event; + + $penalty_ou = $results->[0]->{"id"}; + } + + return $editor->die_event unless $penalty and @$penalty; + $penalty = $penalty->[0]; + + my $last_xact_id_map = {}; + my $clear_meth = "clear_$contact_type"; + + foreach (@$users) { + if ($editor->requestor) { + next unless $editor->allowed("UPDATE_USER", $_->home_ou); + } + + my $usr_penalty = new Fieldmapper::actor::user_standing_penalty; + $usr_penalty->usr($_->id); + $usr_penalty->org_unit($penalty_ou); + $usr_penalty->standing_penalty($penalty->id); + $usr_penalty->staff($staff_id); + $usr_penalty->note($_->$contact_type); + + $editor->create_actor_user_standing_penalty($usr_penalty) or + return $editor->die_event; + + $_->$clear_meth; + $editor->update_actor_user($_) or return $editor->die_event; + + my $updated = $editor->retrieve_actor_user($editor->data); + $last_xact_id_map->{$_->id} = $updated->last_xact_id; + } + + $editor->commit or return $editor->die_event; + + return new OpenILS::Event( + "SUCCESS", payload => {last_xact_id => $last_xact_id_map} + ); +} + +1; diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql index 1c71641ca0..62f1e2f6c2 100644 --- a/Open-ILS/src/sql/Pg/002.schema.config.sql +++ b/Open-ILS/src/sql/Pg/002.schema.config.sql @@ -145,6 +145,52 @@ INSERT INTO config.standing_penalty (id,name,label,block_list,staff_alert) VALUE INSERT INTO config.standing_penalty (id,name,label,block_list,staff_alert) VALUES (28,'STAFF_R','Alerting block on Renew','RENEW', TRUE); INSERT INTO config.standing_penalty (id,name,label) VALUES (29,'INVALID_PATRON_ADDRESS','Patron has an invalid address'); INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency'); +INSERT INTO config.standing_penalty (id, name, label, staff_alert) VALUES + ( + 31, + 'INVALID_PATRON_EMAIL_ADDRESS', + oils_i18n_gettext( + 31, + 'Patron had an invalid email address', + 'csp', + 'label' + ), + TRUE + ), + ( + 32, + 'INVALID_PATRON_DAY_PHONE', + oils_i18n_gettext( + 32, + 'Patron had an invalid daytime phone number', + 'csp', + 'label' + ), + TRUE + ), + ( + 33, + 'INVALID_PATRON_EVENING_PHONE', + oils_i18n_gettext( + 33, + 'Patron had an invalid evening phone number', + 'csp', + 'label' + ), + TRUE + ), + ( + 34, + 'INVALID_PATRON_OTHER_PHONE', + oils_i18n_gettext( + 34, + 'Patron had an invalid other phone number', + 'csp', + 'label' + ), + TRUE + ); + SELECT SETVAL('config.standing_penalty_id_seq', 100); diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.mark-email-and-phone-invalid.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.mark-email-and-phone-invalid.sql new file mode 100644 index 0000000000..0e3fb65f16 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.mark-email-and-phone-invalid.sql @@ -0,0 +1,59 @@ +-- Evergreen DB patch XXXX.data.mark-email-and-phone-invalid.sql +-- +-- Add org unit settings and standing penalty types to support +-- the mark email/phone invalid features. +-- +BEGIN; + +-- check whether patch can be applied +SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + + +INSERT INTO config.standing_penalty (id, name, label, staff_alert) VALUES + ( + 31, + 'INVALID_PATRON_EMAIL_ADDRESS', + oils_i18n_gettext( + 31, + 'Patron had an invalid email address', + 'csp', + 'label' + ), + TRUE + ), + ( + 32, + 'INVALID_PATRON_DAY_PHONE', + oils_i18n_gettext( + 32, + 'Patron had an invalid daytime phone number', + 'csp', + 'label' + ), + TRUE + ), + ( + 33, + 'INVALID_PATRON_EVENING_PHONE', + oils_i18n_gettext( + 33, + 'Patron had an invalid evening phone number', + 'csp', + 'label' + ), + TRUE + ), + ( + 34, + 'INVALID_PATRON_OTHER_PHONE', + oils_i18n_gettext( + 34, + 'Patron had an invalid other phone number', + 'csp', + 'label' + ), + TRUE + ); + + +COMMIT; diff --git a/Open-ILS/src/templates/actor/user/register.tt2 b/Open-ILS/src/templates/actor/user/register.tt2 index 4e7c7fef20..554127d5e3 100644 --- a/Open-ILS/src/templates/actor/user/register.tt2 +++ b/Open-ILS/src/templates/actor/user/register.tt2 @@ -46,6 +46,7 @@