From 6c1a9160b47bfdf303fc347902fb8e11ca6985ec 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 closely follows 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 [both optionally, per OU setting] 1) create a corresponding standing penalty against the user, for staff to notice next time they bring up said patron in the staff client, and 2) create a patron note. Related penalties (but not notes) will be cleared 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 other 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 --- .../src/perlmods/lib/OpenILS/Application/Actor.pm | 154 +++++++++++++++++++- .../src/perlmods/lib/OpenILS/Utils/BadContact.pm | 155 +++++++++++++++++++++ Open-ILS/src/sql/Pg/002.schema.config.sql | 42 ++++++ Open-ILS/src/sql/Pg/950.data.seed-values.sql | 64 ++++++++- .../XXXX.data.mark-email-and-phone-invalid.sql | 134 ++++++++++++++++++ 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 + 15 files changed, 640 insertions(+), 6 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 c54debf431..f92f291d42 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; @@ -876,6 +895,58 @@ 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 $CPM = $OpenILS::Utils::BadContact::CONTACT_PENALTY_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" => [map { $_->{penalty_name} } values(%$CPM)]}, + "+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, $field_map); + + # 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, $field_map) = each(%$CPM)) { + if ($old_patron->$field ne $new_patron->$field) { + push @penalties_to_clear, grep { + $_->standing_penalty->name eq $field_map->{penalty_name} + } @$bad_contact_penalties; + } + } + + foreach (@penalties_to_clear) { + # XXX this "archives" penalties, in the terminology of the staff + # client. Would it be better just to delete them? + $_->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 { @@ -4409,4 +4480,85 @@ 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)", 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..54c1675e09 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/BadContact.pm @@ -0,0 +1,155 @@ +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"; + +my %_PHONE_VARIABLES = ( + penalty_ou_setting => "circ.patron_invalid_phone_apply_penalty", + note_ou_setting => "circ.patron_invalid_phone_save_as_note" +); + +our $CONTACT_PENALTY_MAP = { + email => { + penalty_name => "INVALID_PATRON_EMAIL_ADDRESS", + penalty_ou_setting => "circ.patron_invalid_email_apply_penalty", + note_ou_setting => "circ.patron_invalid_email_save_as_note" + }, + day_phone => { + penalty_name => "INVALID_PATRON_DAY_PHONE", + %_PHONE_VARIABLES + }, + evening_phone => { + penalty_name => "INVALID_PATRON_EVENING_PHONE", + %_PHONE_VARIABLES + }, + other_phone => { + penalty_name => "INVALID_PATRON_OTHER_PHONE", + %_PHONE_VARIABLES + } +}; + +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 $CONTACT_PENALTY_MAP->{$contact_type}) { + return new OpenILS::Event( + "BAD_PARAMS", note => "contact_type argument invalid" + ); + } + + my $CPM = $CONTACT_PENALTY_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 use these two for caching OU settings + my $want_note = {}; + my $want_penalty = {}; + + # we'll need this to apply user penalties (OU settings allowing) + my $penalty = $editor->search_config_standing_penalty({ + name => $CPM->{penalty_name} + }); + + return $editor->die_event unless $penalty and @$penalty; + $penalty = $penalty->[0]; + + my $last_xact_id_map = {}; + + foreach (@$users) { + my $ou = $_->home_ou; + if ($editor->requestor) { + next unless $editor->allowed("UPDATE_USER", $ou); + } + + if (not exists($want_note->{$ou})) { + $want_note->{$ou} = + $U->ou_ancestor_setting_value( + $ou, $CPM->{note_ou_setting}, $editor + ) || 0; + } + if (not exists($want_penalty->{$ou})) { + $want_penalty->{$ou} = + $U->ou_ancestor_setting_value( + $ou, $CPM->{penalty_ou_setting}, $editor + ) || 0; + } + + if ($want_note->{$ou}) { + my $note = new Fieldmapper::actor::usr_note; + $note->usr($_->id); + $note->creator($staff_id); + + $note->title($penalty->label); # sic, re-use csp label in note + $note->value($_->$contact_type); + if ($addl_note) { + $note->value($note->value . "\n" . $addl_note); + } + + $editor->create_actor_usr_note($note) or return $editor->die_event; + } + + if ($want_penalty->{$ou}) { + my $usr_penalty = new Fieldmapper::actor::user_standing_penalty; + $usr_penalty->usr($_->id); + $usr_penalty->org_unit($penalty_ou || $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; + } + + my $clear_meth = "clear_$contact_type"; + $_->$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; + + 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 8eba8b3104..bff7ac206e 100644 --- a/Open-ILS/src/sql/Pg/002.schema.config.sql +++ b/Open-ILS/src/sql/Pg/002.schema.config.sql @@ -144,6 +144,48 @@ INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (27,'STAFF INSERT INTO config.standing_penalty (id,name,label,block_list) VALUES (28,'STAFF_R','Alerting block on Renew','RENEW'); 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) VALUES + ( + 31, + 'INVALID_PATRON_EMAIL_ADDRESS', + oils_i18n_gettext( + 31, + 'Patron had an invalid email address', + 'csp', + 'label' + ) + ), + ( + 32, + 'INVALID_PATRON_DAY_PHONE', + oils_i18n_gettext( + 32, + 'Patron had an invalid daytime phone number', + 'csp', + 'label' + ) + ), + ( + 33, + 'INVALID_PATRON_EVENING_PHONE', + oils_i18n_gettext( + 33, + 'Patron had an invalid evening phone number', + 'csp', + 'label' + ) + ), + ( + 34, + 'INVALID_PATRON_OTHER_PHONE', + oils_i18n_gettext( + 34, + 'Patron had an invalid other phone number', + 'csp', + 'label' + ) + ); + SELECT SETVAL('config.standing_penalty_id_seq', 100); diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index 50b719214d..5310bb59d3 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -4511,8 +4511,70 @@ INSERT into config.org_unit_setting_type 'coust', 'description' ), 'bool', null) +,( + 'circ.patron_invalid_email_save_as_note', + 'circ', + oils_i18n_gettext( + 'circ.patron_invalid_email_save_as_note', + 'Save invalid email in patron note', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.patron_invalid_email_save_as_note', + 'When marking a patron email address invalid, save the email address in a patron note. Notes created in this fashion are not automatically removed when the patron''s email address is updated.', + 'coust', + 'description' + ), + 'bool',null) +,('circ.patron_invalid_email_apply_penalty', + 'circ', + oils_i18n_gettext( + 'circ.patron_invalid_email_apply_penalty', + 'Apply standing penalty to patrons when marking invalid email?', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.patron_invalid_email_apply_penalty', + 'If true, this setting creates a standing penalty of the type "INVALID_PATRON_EMAIL_ADDRESS" against a patron when the patron''s email address is marked invalid. Such penalties are automatically cleared when the patron''s email address is updated.', + 'coust', + 'description' + ), + 'bool', null) +,('circ.patron_invalid_phone_save_as_note', + 'circ', + oils_i18n_gettext( + 'circ.patron_invalid_phone_save_as_note', + 'Save invalid phone number in patron note', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.patron_invalid_phone_save_as_note', + 'When marking a phone number invalid, save the phone number in a patron note.', + 'coust', + 'description' + ), + 'bool', null) +,( + 'circ.patron_invalid_phone_apply_penalty', + 'circ', + oils_i18n_gettext( + 'circ.patron_invalid_phone_apply_penalty', + 'Apply standing penalty to patrons when marking invalid phone number?', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.patron_invalid_phone_apply_penalty', + 'If true, this setting creates a standing penalty against a patron when the patron''s phone number is marked invalid.', + 'coust', + 'description' + ), + 'bool',null +); -; UPDATE config.org_unit_setting_type SET view_perm = (SELECT id FROM permission.perm_list 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..b373b8abb4 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.mark-email-and-phone-invalid.sql @@ -0,0 +1,134 @@ +-- 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.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES ( + 'circ.patron_invalid_email_save_as_note', + 'circ', + oils_i18n_gettext( + 'circ.patron_invalid_email_save_as_note', + 'Save invalid email in patron note', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.patron_invalid_email_save_as_note', + 'When marking a patron email address invalid, save the email address in a patron note. Notes created in this fashion are not automatically removed when the patron''s email address is updated.', + 'coust', + 'description' + ), + 'bool' + ); + +INSERT INTO config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES ( + 'circ.patron_invalid_email_apply_penalty', + 'circ', + oils_i18n_gettext( + 'circ.patron_invalid_email_apply_penalty', + 'Apply standing penalty to patrons when marking invalid email?', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.patron_invalid_email_apply_penalty', + 'If true, this setting creates a standing penalty of the type "INVALID_PATRON_EMAIL_ADDRESS" against a patron when the patron''s email address is marked invalid. Such penalties are automatically cleared when the patron''s email address is updated.', + 'coust', + 'description' + ), + 'bool' + ); + +INSERT INTO config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES ( + 'circ.patron_invalid_phone_save_as_note', + 'circ', + oils_i18n_gettext( + 'circ.patron_invalid_phone_save_as_note', + 'Save invalid phone number in patron note', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.patron_invalid_phone_save_as_note', + 'When marking a phone number invalid, save the phone number in a patron note.', + 'coust', + 'description' + ), + 'bool' + ); + +INSERT INTO config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES ( + 'circ.patron_invalid_phone_apply_penalty', + 'circ', + oils_i18n_gettext( + 'circ.patron_invalid_phone_apply_penalty', + 'Apply standing penalty to patrons when marking invalid phone number?', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.patron_invalid_phone_apply_penalty', + 'If true, this setting creates a standing penalty against a patron when the patron''s phone number is marked invalid.', + 'coust', + 'description' + ), + 'bool' + ); + +INSERT INTO config.standing_penalty (id, name, label) VALUES + ( + 31, + 'INVALID_PATRON_EMAIL_ADDRESS', + oils_i18n_gettext( + 31, + 'Patron had an invalid email address', + 'csp', + 'label' + ) + ), + ( + 32, + 'INVALID_PATRON_DAY_PHONE', + oils_i18n_gettext( + 32, + 'Patron had an invalid daytime phone number', + 'csp', + 'label' + ) + ), + ( + 33, + 'INVALID_PATRON_EVENING_PHONE', + oils_i18n_gettext( + 33, + 'Patron had an invalid evening phone number', + 'csp', + 'label' + ) + ), + ( + 34, + 'INVALID_PATRON_OTHER_PHONE', + oils_i18n_gettext( + 34, + 'Patron had an invalid other phone number', + 'csp', + 'label' + ) + ); + + +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 @@