From: Terran McCanna Date: Thu, 5 Aug 2021 22:36:17 +0000 (-0400) Subject: Quipu Online Account Renewal - Squashed X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=c7f6e10541b96742ade840654c31d195de53cc30;p=evergreen%2Fpines.git Quipu Online Account Renewal - Squashed Online Renewal - Start of working branch - Creates placeholders for new English & Spanish pages - Adds ability for those pages to load - Create div on My Account main page & prefs page to hold renewal message or button - Adds code to check whether or not patron is eligible to renew online or not: * patron has not already had one temporary renewal * patron account is still active * patron account expiration date is no more than 30 days in the future * patron account is not barred * patron account does not have a staff-added blocking alert * patron does not owe any fines * patron is not in collections (even if patron pays fines, staff still need to remove collections note) * patron is in one of these perm groups: Patron (2), Friend (14), GLS (56), Homebound (53), Trustee (22), Quipu (64), Digital Only (60) * patron has a valid billing address * patron has a valid day phone NOTE: New standing penalty needs to be created called PATRON_TEMP_RENEWAL. It should display a staff alert and have the label "Patron was given a 30-day temporary account renewal. Please archive this message after the account is fully renewed." It should not create any type of block on its own. The ID of the new standing penalty needs to be updated in the Account.pm file. Signed-off-by: Terran McCanna Online Renewal - Add standing penalty Signed-off-by: Terran McCanna Online Renewal: Flesh out patron info in API Signed-off-by: Terran McCanna Online Renewal - Create new API instead of piggybacking on vital_stats New api: open-ils.actor.user.opac.renewal Signed-off-by: Terran McCanna Online Renewal - Create page to hold embedded form Signed-off-by: Terran McCanna Online Renewal - First stab at adding erenew to Ecard.pm Signed-off-by: Terran McCanna typo fix Signed-off-by: Chris Sharp further typo fix Signed-off-by: Chris Sharp even further typo fix Signed-off-by: Chris Sharp even FURTHER typo fix Signed-off-by: Chris Sharp declare the variable, and update it if needed Signed-off-by: Chris Sharp declare the variable once, then update the values Signed-off-by: Chris Sharp Online Renewal - Remove old subroutines we don't use Signed-off-by: Terran McCanna Online Renewal - Add page routing to EGCatLoader Signed-off-by: Terran McCanna Online Renewal - Retrieve patron then update fields to push back Signed-off-by: Terran McCanna Online Renewal - Exceptions for temp renewal Signed-off-by: Terran McCanna Online Renewal - Fix msg declaration error Signed-off-by: Terran McCanna Online Renewal - Get URL path in a different way Signed-off-by: Terran McCanna Online Renewal - fix typos Signed-off-by: Terran McCanna Online Renewal - Progress on writing quipu into to db Still to do: * Actor.pm - failing on standing penalties line 597 * sub update_addresses - creating separate addresses works if mailing & billing are different, but it is not checking the right checkbox * writing new home ou is failing on terran-test, but should work on pines data * after update, opac page doesn't refresh * if this is a temporary (30 day) renewal, need to pass message to quipu telling patron to come to the library * if temp renewal, need to add alert to patron account to inform staff to get proof of identity to do full renewal * need to test voter registration survey on pines test server since I don't have that on my test server Signed-off-by: Terran McCanna Online Renewal - Show Updated Expire Date Without this, the page loads the initial patron expiration date and continues to show the button to renew the account. Signed-off-by: Terran McCanna Online Renewal - Document which patron groups are eligible in code Signed-off-by: Terran McCanna Online Renewal - Now saving and responding okay Signed-off-by: Terran McCanna add column to permission.grp_tree to account for e-renew Online Renewal - tweak SQL - Parentheses on first SQL generated error. - Set erenew default to FALSE instead of TRUE. Signed-off-by: Terran McCanna Online Renewal - See & update permission group setting through staff client Signed-off-by: Terran McCanna Online Renewal - Tweak new standing penalty The ID of the standing penalty has to be under 100 to prevent staff from manually adding it to patron accounts through the client interface. Signed-off-by: Terran McCanna Online Renewal - Add Standing Penalty when Temp Renewal Signed-off-by: Terran McCanna Online Renewal - Use standing penalty names instead of IDs Signed-off-by: Terran McCanna Online Renewal - add temp renewal flag to quipu response Signed-off-by: Terran McCanna Online Renewal - Remove perm group ID references Look up permission groups by name or by e-renewal eligibility flag instead of by specific permission group ID. Signed-off-by: Terran McCanna Online Renewal - Got surveys working Signed-off-by: Terran McCanna Online Renewal - Progress on updating addresses (still issues with checkmarks when changing from 1 to 2 addresses or from 2 to 1 address) Signed-off-by: Terran McCanna Online Renewal - Got address changes to save in the right way Signed-off-by: Terran McCanna Online Renewal - add date to quipu name keyword entry Signed-off-by: Terran McCanna Online Renewal - Add check for valid mailing address Only allow renewal option if neither mailing nor billing addresses have been marked invalid. Signed-off-by: Terran McCanna Online Renewal - Cleanup - Get rid of subroutine I didn't end up using - Make sure a user is logged in before showing the renewal form. Signed-off-by: Terran McCanna Online Renewal - Prevent user from re-submitting renewal after complete Signed-off-by: Terran McCanna --- diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index ee7b13e83a..e62eb22b8c 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -8402,6 +8402,7 @@ SELECT usr, + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.html b/Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.html index fa9fc425a4..60ce9f6cf5 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.html +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.html @@ -114,6 +114,14 @@ {{selected.callerData.usergroup() === 't'}} +
+
+ +
+
+ {{selected.callerData.erenew() == 't'}} +
+
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm index 56df630e79..f808e665f2 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm @@ -2059,6 +2059,69 @@ sub user_opac_vitals { }; } +# PINES ECARD RENEWAL INFO API +__PACKAGE__->register_method( + method => "user_opac_renewal", + api_name => "open-ils.actor.user.opac.renewal", + argc => 1, + authoritative => 1, + signature => { + desc => 'Returns minimal patron info for 3rd party renewal', + params => [ + {desc => 'Authentication token', type => 'string'}, + {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number? + ], + return => { + desc => "A user object." + } + } +); + +sub user_opac_renewal { + my( $self, $client, $auth, $user_id ) = @_; + + my $e = new_editor(authtoken=>$auth); + return $e->event unless $e->checkauth; + + $user_id ||= $e->requestor->id; + + my $user = $e->retrieve_actor_user([ $user_id, { + flesh => 1, + flesh_fields => { + au => ['card', 'billing_address', 'mailing_address'] + } + }]); + + return { + user => { + first_given_name => $user->first_given_name, + second_given_name => $user->second_given_name, + family_name => $user->family_name, + pref_first_given_name => $user->pref_first_given_name, + pref_second_given_name => $user->pref_second_given_name, + pref_family_name => $user->pref_family_name, + day_phone => $user->day_phone, + email => $user->email, + home_ou => $user->home_ou, + barcode => $user->card->barcode, + physical_street1 => $user->billing_address->street1, + physical_street2 => $user->billing_address->street2, + physical_city => $user->billing_address->city, + physical_post_code => $user->billing_address->post_code, + physical_county => $user->billing_address->county, + physical_state => $user->billing_address->state, + physical_country => $user->billing_address->country, + mailing_street1 => $user->mailing_address->street1, + mailing_street2 => $user->mailing_address->street2, + mailing_city => $user->mailing_address->city, + mailing_post_code => $user->mailing_address->post_code, + mailing_county => $user->mailing_address->county, + mailing_state => $user->mailing_address->state, + mailing_country => $user->mailing_address->country + } + }; +} + ##### a small consolidation of related method registrations my $common_params = [ diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/permission.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/permission.pm index 7c7cab6d85..af9ada555a 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/permission.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/permission.pm @@ -16,7 +16,7 @@ use base qw/permission/; __PACKAGE__->table('permission_grp_tree'); __PACKAGE__->columns(Primary => qw/id/); __PACKAGE__->columns(Essential => qw/name parent description perm_interval - application_perm usergroup hold_priority/); + application_perm usergroup hold_priority erenew/); #------------------------------------------------------------------------------- package permission::usr_grp_map; use base qw/permission/; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm index d0a2d8a335..55376abb97 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm @@ -186,10 +186,11 @@ sub load { $self->load_simple("myopac") if $path =~ m:opac/myopac:; # A default page for myopac parts - # maybe make these optional parts of load_patron_reg? - #return $self->load_ecard_form if $path =~ m|opac/ecard/form|; + # PINES - online account registration return $self->load_ecard_submit if $path =~ m|opac/ecard/submit|; - return $self->load_ecard_verify if $path =~ m|opac/ecard/verify|; + + # PINES - online account renewal + return $self->load_simple("renew-account") if $path =~ m|opac/renew-account|; if($path =~ m|opac/login|) { return $self->load_login unless $self->editor->requestor; # already logged in? diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm index 8032aeb608..786a912dda 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm @@ -120,6 +120,19 @@ sub load_myopac_prefs { $self->prepare_extended_user_info; my $user = $self->ctx->{user}; + # PINES - check whether or not to provide account renewal link + if ($self->ctx->{user}->billing_address) { + $self->ctx->{valid_billing_address} = $self->ctx->{user}->billing_address->valid; + } else { + $self->ctx->{valid_billing_address} = 0; + } + if ($self->ctx->{user}->mailing_address) { + $self->ctx->{valid_mailing_address} = $self->ctx->{user}->mailing_address->valid; + } else { + $self->ctx->{valid_mailing_address} = 0; + } + $self->check_account_exp(); + my $lock_usernames = $self->ctx->{get_org_setting}->($e->requestor->home_ou, 'opac.lock_usernames'); if(defined($lock_usernames) and $lock_usernames == 1) { # Policy says no username changes @@ -189,7 +202,6 @@ sub load_myopac_prefs_notify { my $self = shift; my $e = $self->editor; - my $stat = $self->_load_user_with_prefs; return $stat if $stat; @@ -2645,6 +2657,22 @@ sub load_myopac_main { pub => 't' }) ); + + # PINES - need to make sure we're retrieving current info + $self->prepare_extended_user_info; + # PINES - check whether or not to provide account renewal link + if ($self->ctx->{user}->billing_address) { + $self->ctx->{valid_billing_address} = $self->editor->retrieve_actor_user_address($self->ctx->{user}->billing_address)->valid; + } else { + $self->ctx->{valid_billing_address} = 0; + } + if ($self->ctx->{user}->mailing_address) { + $self->ctx->{valid_mailing_address} = $self->editor->retrieve_actor_user_address($self->ctx->{user}->mailing_address)->valid; + } else { + $self->ctx->{valid_mailing_address} = 0; + } + $self->check_account_exp(); + return $self->prepare_fines($limit, $offset) || Apache2::Const::OK; } @@ -3537,4 +3565,144 @@ sub load_password_reset { return Apache2::Const::OK; } +# PINES - check whether patron has standing penalties that should block +# online account renewal +sub has_penalties { + my $self = shift; + my $ctx = $self->ctx; + my $user = $self->ctx->{user}; + my $e = new_editor(xact => 1); + + #I'm sure there is a way to combine the following standing penalty checks, but this is working for now + + #check for INVALID_PATRON_ADDRESS + my $findpenalty_address = $e->search_config_standing_penalty({name => 'INVALID_PATRON_ADDRESS'})->[0]; + my $searchpenalty_address = $e->search_actor_user_standing_penalty({ + usr => $user->id, + standing_penalty => $findpenalty_address->id, + '-or' => [ + {stop_date => undef}, + {stop_date => {'>' => 'now'}} + ] + }); + + #check for INVALID_PATRON_DAY_PHONE + my $findpenalty_phone = $e->search_config_standing_penalty({name => 'INVALID_PATRON_DAY_PHONE'})->[0]; + my $searchpenalty_phone = $e->search_actor_user_standing_penalty({ + usr => $user->id, + standing_penalty => $findpenalty_phone->id, + '-or' => [ + {stop_date => undef}, + {stop_date => {'>' => 'now'}} + ] + }); + + #check for PATRON_IN_COLLECTIONS + my $findpenalty_coll = $e->search_config_standing_penalty({name => 'PATRON_IN_COLLECTIONS'})->[0]; + my $searchpenalty_coll = $e->search_actor_user_standing_penalty({ + usr => $user->id, + standing_penalty => $findpenalty_coll->id, + '-or' => [ + {stop_date => undef}, + {stop_date => {'>' => 'now'}} + ] + }); + + #check for alerting block + my $findpenalty_alertblock = $e->search_config_standing_penalty({name => 'STAFF_CHR'})->[0]; + my $searchpenalty_alertblock = $e->search_actor_user_standing_penalty({ + usr => $user->id, + standing_penalty => $findpenalty_alertblock->id, + '-or' => [ + {stop_date => undef}, + {stop_date => {'>' => 'now'}} + ] + }); + + #check for PATRON_TEMP_RENEWAL + my $findpenalty_temp = $e->search_config_standing_penalty({name => 'PATRON_TEMP_RENEWAL'})->[0]; + my $searchpenalty_temp = $e->search_actor_user_standing_penalty({ + usr => $user->id, + standing_penalty => $findpenalty_temp->id, + '-or' => [ + {stop_date => undef}, + {stop_date => {'>' => 'now'}} + ] + }); + + if (@$searchpenalty_address || @$searchpenalty_coll || @$searchpenalty_phone || @$searchpenalty_alertblock) { + $ctx->{haspenalty} = 1; + } else { + $ctx->{haspenalty} = 0; + } + + if (@$searchpenalty_temp) { + $ctx->{hastemprenew} = 1; + } else { + $ctx->{hastemprenew} = 0; + } + + return; +} + +# PINES - check whether or not to show account renewal link +sub check_account_exp { + my $self = shift; + my $ctx = $self->ctx; + $self->update_dashboard_stats(); + + #make sure patron is in an eligible perm group for renewal + my $grp = new_editor()->retrieve_permission_grp_tree($ctx->{user}->profile); + + if ($grp->erenew eq 't') { + $ctx->{eligible_permgroup} = 1; + } else { + $ctx->{eligible_permgroup} = 0; + } + + #check for various standing penalties that would block an online renewal + $self->has_penalties(); + + #check for other problems that would block an online renewal + if ($ctx->{user}->active ne 't') { #user is no longer active + $ctx->{hasproblem} = 1; + } elsif ($ctx->{haspenalty} eq 1) { #user has a standing penalty block + $ctx->{hasproblem} = 1; + } elsif ($ctx->{user}->barred eq 't') { #user is barred + $ctx->{hasproblem} = 1; + } elsif ($ctx->{valid_billing_address} ne 't') { #user has invalid billing address + $ctx->{hasproblem} = 1; + } elsif ($ctx->{valid_mailing_address} ne 't') { #user has invalid mailing address + $ctx->{hasproblem} = 1; + } else { + $ctx->{hasproblem} = 0; + } + + #determine which message to show (if any) + my $cache = OpenSRF::Utils::Cache->new('global'); + $cache->put_cache('account_renew_ok','false',3600); + + if ($ctx->{hastemprenew} eq 1) { #user already has active temp renewal + $ctx->{account_renew_message} = '
Your account + could only be temporarily renewed because your address changed. Please visit your nearest PINES + library with your current proof of address to complete your account renewal.
'; + } elsif (DateTime->today->add(days=>30) lt $ctx->{user}->expire_date) { + #expiration date is too far in future - don't show message + $ctx->{account_renew_message} = ''; + } elsif ($ctx->{hasproblem} eq 1 or $ctx->{eligible_permgroup} eq 0) { #see other problems above + $ctx->{account_renew_message} = '
Your account is + due for renewal, but it cannot be renewed online. Please visit your nearest PINES + library with your current ID and proof of address to update and renew your account.
'; + } elsif ($ctx->{user_stats}->{fines}->{balance_owed} gt 0) { #user has fines + $ctx->{account_renew_message} = '
Your account + is due for renewal. Please pay your outstanding fines in order to renew your account.
'; + } else { + $ctx->{account_renew_message} = 'Click here to renew your account'; + $cache->put_cache('account_renew_ok','true',3600); + } + + return 1; +} + 1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Ecard.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Ecard.pm index f35c448246..e903e90ed3 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Ecard.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Ecard.pm @@ -5,6 +5,7 @@ use OpenSRF::Utils::Logger qw/$logger/; use OpenSRF::Utils::JSON; use OpenSRF::Utils qw/:datetime/; use OpenILS::Utils::Fieldmapper; +use OpenSRF::Utils::Cache; use OpenILS::Application::AppUtils; use OpenILS::Utils::CStoreEditor qw/:funcs/; use OpenILS::Event; @@ -15,7 +16,39 @@ use Digest::MD5 qw(md5_hex); $Data::Dumper::Indent = 0; my $U = 'OpenILS::Application::AppUtils'; -my @api_fields = ( +my $update_type = 'register'; + +my @api_fields_renew = ( + {name => 'vendor_username', required => 1}, + {name => 'vendor_password', required => 1}, + {name => 'email', class => 'au'}, + {name => 'day_phone', class => 'au', required => 1}, + {name => 'home_ou', class => 'au'}, + {name => 'pref_first_given_name', class => 'au'}, + {name => 'pref_second_given_name', class => 'au'}, + {name => 'pref_family_name', class => 'au'}, + {name => 'physical_id', class => 'aua'}, + {name => 'physical_street1', class => 'aua'}, + {name => 'physical_street1_name'}, + {name => 'physical_street2', class => 'aua'}, + {name => 'physical_city', class => 'aua'}, + {name => 'physical_post_code', class => 'aua'}, + {name => 'physical_county', class => 'aua'}, + {name => 'physical_state', class => 'aua'}, + {name => 'physical_country', class => 'aua'}, + {name => 'mailing_id', class => 'aua'}, + {name => 'mailing_street1', class => 'aua'}, + {name => 'mailing_street1_name'}, + {name => 'mailing_street2', class => 'aua'}, + {name => 'mailing_city', class => 'aua'}, + {name => 'mailing_post_code', class => 'aua'}, + {name => 'mailing_county', class => 'aua'}, + {name => 'mailing_state', class => 'aua'}, + {name => 'mailing_country', class => 'aua'}, + {name => 'voter_registration', class => 'asvr'} +); + +my @api_fields_register = ( {name => 'vendor_username', required => 1}, {name => 'vendor_password', required => 1}, {name => 'first_given_name', class => 'au', required => 1}, @@ -30,9 +63,9 @@ my @api_fields = ( {name => 'ident_type', class => 'au', required => 1}, {name => 'ident_value', class => 'au', required => 1}, {name => 'ident_value2', - class => 'au', - notes => "AKA parent/guardian", - required_if => 'Patron is less than 18 years old' + class => 'au', + notes => "AKA parent/guardian", + required_if => 'Patron is less than 18 years old' }, {name => 'pref_first_given_name', class => 'au'}, {name => 'pref_second_given_name', class => 'au'}, @@ -58,99 +91,6 @@ my @api_fields = ( {name => 'in_house_registration', required => 1}, ); - -# TODO: wrap the following in a check for a library setting as to whether or not -# to require emailed verification - -## Random 6-character alpha-numeric code that avoids look-alike characters -## https://ux.stackexchange.com/questions/53341/are-there-any-letters-numbers-that-should-be-avoided-in-an-id -## Also exclude vowels to avoid creating any real (potentially offensive) words. -#my @code_chars = ('C','D','F','H','J'..'N','P','R','T','V','W','X','3','4','7','9'); -#sub generate_verify_code { -# my $string = ''; -# $string .= $code_chars[rand @code_chars] for 1..6; -# return $string; -#} -# -# -## only if we're verifying the card via email -#sub load_ecard_verify { -# my $self = shift; -# my $cgi = $self->cgi; -# $self->collect_header_footer; -# -# # Loading the form. -# return Apache2::Const::OK if $cgi->request_method eq 'GET'; -# -# #$self->verify_ecard; -# return Apache2::Const::OK; -#} -# -#sub verify_ecard { -# my $self = shift; -# my $cgi = $self->cgi; -# my $ctx = $self->ctx; -# $self->log_params; -# -# my $verify_code = $ctx->{verify_code} = $cgi->param('verification_code'); -# my $barcode = $ctx->{barcode} = $cgi->param('barcode'); -# -# $ctx->{verify_failed} = 1; -# -# my $e = new_editor(); -# -# my $au = $e->search_actor_user({ -# profile => $PROVISIONAL_ECARD_GRP, -# ident_type => $ECARD_VERIFY_IDENT, -# ident_value => $verify_code -# })->[0]; -# -# if (!$au) { -# $logger->warn( -# "ECARD: No provisional ecard found with code $verify_code"); -# sleep 2; # Mitigate brute-force attacks -# return; -# } -# -# my $card = $e->search_actor_card({ -# usr => $au->id, -# barcode => $barcode -# })->[0]; -# -# if (!$card) { -# $logger->warn("ECARD: Failed to match verify code ". -# "($verify_code) with provided barcode ($barcode)"); -# sleep 2; # Mitigate brute-force attacks -# return; -# } -# -# # Verification looks good. Update the account. -# -# my $grp = new_editor()->retrieve_permission_grp_tree($FULL_ECARD_GRP); -# -# $au->profile($grp->id); -# $au->expire_date( -# DateTime->now(time_zone => 'local')->add( -# seconds => interval_to_seconds($grp->perm_interval))->iso8601() -# ); -# -# $e->xact_begin; -# -# unless ($e->update_actor_user($au)) { -# $logger->error("ECARD update failed for $barcode: " . $e->die_event); -# return; -# } -# -# $e->commit; -# $logger->info("ECARD: Update to full ecard succeeded for $barcode"); -# -# $ctx->{verify_success} = 1; -# $ctx->{verify_failed} = 0; -# -# return; -#} - - sub log_params { my $self = shift; my $cgi = $self->cgi; @@ -172,7 +112,7 @@ sub handle_testmode_api { # Strip data we don't want to publish. my @doc_fields; - for my $field_info (@api_fields) { + for my $field_info (@api_fields_register) { my $doc_info = {}; for my $info_key (keys %$field_info) { $doc_info->{$info_key} = $field_info->{$info_key} @@ -183,6 +123,7 @@ sub handle_testmode_api { $ctx->{response}->{messages} = [fields => \@doc_fields]; $ctx->{response}->{status} = 'API_OK'; + return $self->compile_response; } @@ -202,6 +143,7 @@ sub handle_datamode_api { } $ctx->{response}->{status} = 'DATA_OK'; + return $self->compile_response; } @@ -210,6 +152,23 @@ sub load_ecard_submit { my $ctx = $self->ctx; my $cgi = $self->cgi; + #determine whether this is a new registration or a renewal + if ($cgi->param('patron_id') > 1) { + $update_type = 'renew'; + } else { + $update_type = 'register'; + } + + #If this is a renewal, double-check that they are eligible to renew + my $cache = OpenSRF::Utils::Cache->new('global'); + if ($update_type eq 'renew') { + if ($cache->get_cache("account_renew_ok") && $cache->get_cache("account_renew_ok") eq 'true') { + } else { + $logger->error("ERENEW - User not in correct status to renew account"); + return $self->compile_response; + } + } + $self->log_params; my $testmode = $cgi->param('testmode') || ''; @@ -237,20 +196,53 @@ sub load_ecard_submit { return $self->handle_testmode_api if $testmode eq 'API'; return $self->handle_datamode_api($datamode) if $datamode; - return $self->compile_response unless $self->make_user; - return $self->compile_response unless $self->add_addresses; - return $self->compile_response unless $self->check_dupes; - return $self->compile_response unless $self->add_card; - return $self->compile_response unless $self->add_survey_responses; - return $self->compile_response unless $self->save_user; - return $self->compile_response unless $self->add_usr_settings; - return $self->compile_response if $ctx->{response}->{status}; - - $U->create_events_for_hook( - 'au.create.ecard', $ctx->{user}, $ctx->{user}->home_ou); + # Accommodate reg vs renew + if ($update_type eq 'register') { + return $self->compile_response unless $self->make_user; + return $self->compile_response unless $self->add_addresses; + return $self->compile_response unless $self->check_dupes; + return $self->compile_response unless $self->add_card; + return $self->compile_response unless $self->add_survey_responses; + return $self->compile_response unless $self->save_user; + return $self->compile_response unless $self->add_usr_settings; + return $self->compile_response if $ctx->{response}->{status}; + + $U->create_events_for_hook( + 'au.create.ecard', $ctx->{user}, $ctx->{user}->home_ou); + } else { + return $self->compile_response unless $self->update_user; + return $self->compile_response unless $self->update_addresses; + return $self->compile_response unless $self->add_survey_responses; + return $self->compile_response unless $self->save_user; + return $self->compile_response if $ctx->{response}->{status}; + } + # Add extra info to response message $ctx->{response}->{status} = 'OK'; - $ctx->{response}->{barcode} = $ctx->{user}->card->barcode; + + if ($update_type eq 'renew') { + #New expiration date + $ctx->{response}->{expire_date} = $ctx->{user}->expire_date; + #Mark whether this is a temporary renewal or not + my $findpenalty_temp = $e->search_config_standing_penalty({name => 'PATRON_TEMP_RENEWAL'})->[0]; + my $searchpenalty_temp = $e->search_actor_user_standing_penalty({ + usr => $cgi->param('patron_id'), + standing_penalty => $findpenalty_temp->id, + '-or' => [ + {stop_date => undef}, + {stop_date => {'>' => 'now'}} + ] + }); + if (@$searchpenalty_temp) { + $ctx->{response}->{temp_renew} = 1; + } else { + $ctx->{response}->{temp_renew} = 0; + } + #set renewal flag in cache to false to prevent user from refreshing the page and submitting again + $cache->put_cache('account_renew_ok','false',3600); + } else { + $ctx->{response}->{barcode} = $ctx->{user}->card->barcode; + } return $self->compile_response; } @@ -292,28 +284,18 @@ sub verify_vendor_host { return 1; } - sub compile_response { my $self = shift; my $ctx = $self->ctx; + $self->apache->content_type("application/json; charset=utf-8"); $ctx->{response} = OpenSRF::Utils::JSON->perl2JSON($ctx->{response}); $logger->info("ECARD responding with " . $ctx->{response}); - return Apache2::Const::OK; -} -my %keep_case = (usrname => 1, passwd => 1, email => 1); -sub upperclense { - my $self = shift; - my $field = shift; - my $value = shift; - $value = uc($value) unless $keep_case{$field}; - $value = lc($value) if $field eq 'email'; # force it - $value =~ s/(^\s*|\s*$)//g; - return $value; + return Apache2::Const::OK; } -# Create actor.usr perl object and populate column data +# Create actor.usr perl object and populate column data (for new registration) sub make_user { my $self = shift; my $ctx = $self->ctx; @@ -325,6 +307,7 @@ sub make_user { $au->isnew(1); $au->net_access_level(1); # Filtered $au->name_keywords($in_house ? 'quipu_inhouse' : 'quipu_remote'); + my $home_ou = $cgi->param('home_ou'); my $perm_grp = $U->ou_ancestor_setting_value( @@ -340,7 +323,7 @@ sub make_user { seconds => interval_to_seconds($grp->perm_interval))->iso8601() ); - for my $field_info (@api_fields) { + for my $field_info (@api_fields_register) { my $field = $field_info->{name}; next unless $field_info->{class} eq 'au'; @@ -358,6 +341,7 @@ sub make_user { $val = undef if $field eq 'day_phone' && $val eq '--'; $self->verify_dob($val) if $field eq 'dob' && $val; + $au->$field($val); } @@ -366,6 +350,84 @@ sub make_user { return $ctx->{user} = $au; } +# If existing account, update instead of create +sub update_user { + + my $self = shift; + my @extra_flesh = @_; + my $e = $self->editor; + my $ctx = $self->ctx; + my $cgi = $self->cgi; + + # Grab user id, retrieve patron info from db and create patron object + my $patron_id = $cgi->param('patron_id'); + + my $au = $self->editor->retrieve_actor_user([$patron_id, + { + flesh => 1, + flesh_fields => { + au => ['billing_address', 'mailing_address', 'groups', 'permissions', 'standing_penalties'] + } + } + ]); + #indicate that this is an update, not a new record + $au->isnew(0); + + # Replace values in patron object with new data + + # Need to append new keyword for use in reports later + my $orig_kw = $au->name_keywords; + my $dt = DateTime->now; + my $dty = $dt->year; + my $dtm = $dt->month; + if ($orig_kw ne '') { + $au->name_keywords("$orig_kw quipu_renew_$dty$dtm"); + } else { + $au->name_keywords("quipu_renew_$dty$dtm"); + } + + # Temp renewal is only 30 days, otherwise use perm_interval + # If perm group is Homebound or GLS, allow full renewal + my $temp_renewal = $cgi->param('temp_renewal'); + my $grp = new_editor()->retrieve_permission_grp_tree($au->profile); + + if ($temp_renewal eq '1' && $grp->name ne 'GLS' && $grp->name ne 'Homebound') { + $au->expire_date( + DateTime->now(time_zone => 'local')->add( + seconds => interval_to_seconds('30 days'))->iso8601() + ); + # Add temp renewal standing penalty to account + $self->apply_temp_renewal_penalty; + } else { + $au->expire_date( + DateTime->now(time_zone => 'local')->add( + seconds => interval_to_seconds($grp->perm_interval))->iso8601() + ); + } + + # loop through fields submitted by quipu + for my $field_info (@api_fields_renew) { + my $field = $field_info->{name}; + next unless $field_info->{class} eq 'au'; + + my $val = $cgi->param($field); + + if ($field_info->{required} && !$val) { + my $msg = "Value required for field: '$field'"; + $ctx->{response}->{status} = 'INVALID_PARAMS'; + push(@{$ctx->{response}->{messages}}, $msg); + $logger->error("E-RENEW $msg"); + } + + $val = undef if $field eq 'day_phone' && $val eq '--'; + $val = $au->home_ou if $field eq 'home_ou' && $val eq ''; + + $au->$field($val); + } + + return $ctx->{user} = $au; +} + # Card generation must occur after the user is saved in the DB. sub add_card { my $self = shift; @@ -447,16 +509,6 @@ sub verify_dob { return 1; } -# returns true if the addresses contain all of the same values. -sub addrs_match { - my ($self, $addr1, $addr2) = @_; - for my $field ($addr1->real_fields) { - return 0 if ($addr1->$field() || '') ne ($addr2->$field() || ''); - } - return 1; -} - - sub add_addresses { my $self = shift; my $cgi = $self->cgi; @@ -484,7 +536,7 @@ sub add_addresses { # Confirm we have values for all of the required fields. # Apply values to our in-progress address object. - for my $field_info (@api_fields) { + for my $field_info (@api_fields_register) { my $field = $field_info->{name}; next unless $field =~ /physical|mailing/; next if $field =~ /street1_/; @@ -518,6 +570,90 @@ sub add_addresses { return 1; } +sub update_addresses { + my $self = shift; + my $cgi = $self->cgi; + my $ctx = $self->ctx; + my $e = $ctx->{editor}; + my $user = $ctx->{user}; + + my $physical_addr = Fieldmapper::actor::user_address->new; + $physical_addr->id($user->billing_address->id); + $physical_addr->usr($user->id); + $physical_addr->address_type('PHYSICAL'); + $physical_addr->within_city_limits($user->billing_address->within_city_limits); + $physical_addr->valid('t'); + $physical_addr->pending('f'); + + my $mailing_addr = Fieldmapper::actor::user_address->new; + $mailing_addr->id($user->mailing_address->id); + $mailing_addr->usr($user->id); + $mailing_addr->address_type('MAILING'); + $mailing_addr->within_city_limits($user->mailing_address->within_city_limits); + $mailing_addr->valid('t'); + $mailing_addr->pending('f'); + + # Confirm we have values for all of the required fields. + # Apply values to our in-progress address object. + for my $field_info (@api_fields_renew) { + my $field = $field_info->{name}; + next unless $field =~ /physical|mailing/; + next if $field =~ /street1_/; + + my $val = $cgi->param($field); + + if ($field_info->{required} && !$val) { + my $msg = "Value required for field: '$field'"; + $ctx->{response}->{status} = 'INVALID_PARAMS'; + push(@{$ctx->{response}->{messages}}, $msg); + $logger->error("E-RENEW $msg"); + } + + if ($field =~ /physical/) { + (my $col_field = $field) =~ s/physical_//g; + $physical_addr->$col_field($val) if $val; + } else { + (my $col_field = $field) =~ s/mailing_//g; + $mailing_addr->$col_field($val) if $val; + } + } + + # Determine what exactly to do with addresses + if ($physical_addr->id eq $mailing_addr->id && $physical_addr->street1 eq $mailing_addr->street1) { + # if one address & stays at one address, just update it (don't need to do both physical & mailing) + $mailing_addr->isnew(0); + $mailing_addr->ischanged(1); + } elsif ($physical_addr->id eq $mailing_addr->id && $physical_addr->street1 ne $mailing_addr->street1) { + # if one address splitting to two addresses, update the first and create a second address entry + $physical_addr->isnew(0); + $physical_addr->ischanged(1); + $mailing_addr->isnew(1); + $mailing_addr->id(-1); + } elsif ($physical_addr->id ne $mailing_addr->id && $physical_addr->street1 eq $mailing_addr->street1) { + # if there were previously 2 addresses, but there is only one address now, use the updated single address entry for both + $physical_addr->isnew(0); + $physical_addr->ischanged(1); + $mailing_addr->isnew(0); + $mailing_addr->ischanged(1); + $mailing_addr->id($physical_addr->id); + } else { + # otherwise, update existing entries + $physical_addr->isnew(0); + $physical_addr->ischanged(1); + $mailing_addr->isnew(0); + $mailing_addr->ischanged(1); + } + + # exit if there were any errors above. + return undef if $ctx->{response}->{status}; + + $user->billing_address($physical_addr); + $user->mailing_address($mailing_addr); + $user->addresses([$physical_addr, $mailing_addr]); + + return 1; +} + sub add_usr_settings { my $self = shift; my $cgi = $self->cgi; @@ -542,9 +678,10 @@ sub add_survey_responses { my $answer = $cgi->param('voter_registration'); my $survey_response = Fieldmapper::action::survey_response->new; + $survey_response->id(-1); $survey_response->isnew(1); - $survey_response->survey(1); # voter registration survey + $survey_response->survey(1); $survey_response->question(1); $survey_response->answer($answer); @@ -552,35 +689,6 @@ sub add_survey_responses { return 1; } -# TODO: this is KCLS-specific, but maybe we can make it something -# generic for adding stat cats to the patron - -#sub add_stat_cats { -# my $self = shift; -# my $cgi = $self->cgi; -# my $user = $self->ctx->{user}; -# -# my $ds_map = Fieldmapper::actor::stat_cat_entry_user_map->new; -# $ds_map->isnew(1); -# $ds_map->stat_cat(12); -# $ds_map->stat_cat_entry('KCLS'); -# -# my $events = $cgi->param('events_mailing'); -# my $em_map = Fieldmapper::actor::stat_cat_entry_user_map->new; -# $em_map->isnew(1); -# $em_map->stat_cat(3); -# $em_map->stat_cat_entry($events ? 'Y' : 'N'); -# -# my $foundation = $cgi->param('foundation_mailing'); -# my $fm_map = Fieldmapper::actor::stat_cat_entry_user_map->new; -# $fm_map->isnew(1); -# $fm_map->stat_cat(4); -# $fm_map->stat_cat_entry($foundation ? 'Y' : 'N'); -# -# $user->stat_cat_entries([$ds_map, $em_map, $fm_map]); -# return 1; -#} - # Returns true if no dupes found, false if dupes are found. sub check_dupes { my $self = shift; @@ -614,41 +722,6 @@ sub check_dupes { $logger->info("ECARD found potential duplicate patrons: @$ids"); -# if (my $streetname = $self->cgi->param('physical_street1_name')) { -# # We found matching patrons. Perform a secondary check on the -# # address street name only. -# -# $logger->info("ECARD secondary search on street name: $streetname"); -# -# my $addr_ids = $e->search_actor_user_address( -# { usr => $ids, -# street1 => {'~*' => "(^| )$streetname( |\$)"} -# }, {idlist => 1} -# ); -# -# if (@$addr_ids) { -# # we don't really care what patrons match at this point, -# # only whether a match is found. -# $ids = [1]; -# $logger->info("ECARD secondary address check match(es) ". -# "found on address(es) @$addr_ids"); -# -# } else { -# $ids = []; -# $logger->info( -# "ECARD secondary address check found no matches"); -# } -# -# } else { -# $ids = []; -# # unclear if this is a possibility -- err on the side of allowing -# # the registration. -# $logger->info("ECARD found possible patron match but skipping ". -# "secondary street name check -- no street name was provided"); -# } -# -# return 1 if @$ids == 0; - $ctx->{response}->{status} = 'DUPLICATE'; $ctx->{response}->{messages} = ['first_given_name', 'family_name', 'dob']; @@ -661,6 +734,7 @@ sub save_user { my $ctx = $self->ctx; my $cgi = $self->cgi; my $user = $ctx->{user}; + my $update_type = $user->isnew; my $resp = $U->simplereq( 'open-ils.actor', @@ -671,11 +745,18 @@ sub save_user { $resp = {textcode => 'UNKNOWN_ERROR'} unless $resp; if ($U->is_event($resp)) { + my $msg = ''; - my $msg = "Error creating user account: " . $resp->{textcode}; - $logger->error("ECARD: $msg"); + if ($update_type eq '1') { + $msg = "Error creating user account: " . $resp->{textcode}; + $logger->error("ECARD: $msg"); + $ctx->{response}->{status} = 'CREATE_ERR'; + } else { + $msg = "Error updating user account: " . $resp->{textcode}; + $logger->error("E-RENEW: $msg"); + $ctx->{response}->{status} = 'UPDATE_ERR'; + } - $ctx->{response}->{status} = 'CREATE_ERR'; $ctx->{response}->{messages} = [{msg => $msg, pid => $$}]; return 0; @@ -685,5 +766,48 @@ sub save_user { return 1; } +sub apply_temp_renewal_penalty { + + my $self = shift; + my $ctx = $self->ctx; + my $cgi = $self->cgi; + my $patron_id = $cgi->param('patron_id'); + + my $e = new_editor(xact => 1); + my $ptype = $e->search_config_standing_penalty({name => 'PATRON_TEMP_RENEWAL'})->[0]; + + my $penalty = Fieldmapper::actor::user_standing_penalty->new; + $penalty->usr($patron_id); + $penalty->org_unit(1); + $penalty->standing_penalty($ptype->id); + + my $aum = Fieldmapper::actor::usr_message->new; + $aum->create_date('now'); + $aum->sending_lib(1); + $aum->title('Temporary Account Renewal'); + $aum->usr($penalty->usr); + $aum->message('Patron renewed online with an address change so was given a 30-day + temporary account renewal. Please archive this message after the address is + verified and the renewal date extended.'); + $aum->pub(0); + + $aum = $e->create_actor_usr_message($aum); + unless($aum) { + $e->rollback; + return 0; + } + + $penalty->usr_message($aum->id); + + unless($e->create_actor_user_standing_penalty($penalty)) { + $e->rollback; + return 0; + } + + $e->commit; + return 1; +} + + 1; diff --git a/Open-ILS/src/sql/Pg/006.schema.permissions.sql b/Open-ILS/src/sql/Pg/006.schema.permissions.sql index 05d38a6243..c1f1622194 100644 --- a/Open-ILS/src/sql/Pg/006.schema.permissions.sql +++ b/Open-ILS/src/sql/Pg/006.schema.permissions.sql @@ -39,7 +39,8 @@ CREATE TABLE permission.grp_tree ( perm_interval INTERVAL DEFAULT '3 years'::interval NOT NULL, description TEXT, application_perm TEXT, - hold_priority INT NOT NULL DEFAULT 0 + hold_priority INT NOT NULL DEFAULT 0, + erenew BOOL NOT NULL DEFAULT TRUE ); CREATE INDEX grp_tree_parent_idx ON permission.grp_tree (parent); 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 828d560e2d..1cbbf70457 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -91,7 +91,8 @@ INSERT INTO config.standing_penalty (id, name, label, staff_alert, org_depth) VA TRUE, 0 ); - +-- Temp renewal penalty must be under 100 to prevent staff from manually adding it through client interface +INSERT INTO config.standing_penalty (id, name, label, staff_alert, org_depth) VALUES (90, 'PATRON_TEMP_RENEWAL', 'Patron was given a 30-day temporary account renewal. Please archive this message after the account is fully renewed.', TRUE, 0); SELECT SETVAL('config.standing_penalty_id_seq', 100); diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX-quipu-standing_penalty.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX-quipu-standing_penalty.sql new file mode 100644 index 0000000000..e7d82fd51b --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX-quipu-standing_penalty.sql @@ -0,0 +1,11 @@ +BEGIN; + +-- ID has to be under 100 in order to prevent it from appearing as a dropdown in the patron editor. + +INSERT INTO config.standing_penalty (id, name, label, staff_alert, org_depth) +VALUES (90, 'PATRON_TEMP_RENEWAL', + 'Patron was given a 30-day temporary account renewal. + Please archive this message after the account is fully renewed.', TRUE, 0 + ); + +COMMIT; \ No newline at end of file diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.erenew_column_pgt.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.erenew_column_pgt.sql new file mode 100644 index 0000000000..cf584015e7 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.erenew_column_pgt.sql @@ -0,0 +1,17 @@ +BEGIN; + +ALTER TABLE permission.grp_tree ADD COLUMN erenew BOOL; + +COMMIT; + +BEGIN; + +UPDATE permission.grp_tree SET erenew = FALSE; + +COMMIT; + +BEGIN; + +ALTER TABLE permission.grp_tree ALTER COLUMN erenew SET NOT NULL; + +COMMIT; diff --git a/Open-ILS/src/templates-bootstrap/opac/myopac/main.tt2 b/Open-ILS/src/templates-bootstrap/opac/myopac/main.tt2 index cc07f66c37..8854de091f 100755 --- a/Open-ILS/src/templates-bootstrap/opac/myopac/main.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/myopac/main.tt2 @@ -16,21 +16,14 @@
[% l("PINES Account Tips") %]
- - [% l("Account Expiration Date") %]: + + [% l("Account Expiration Date") %]: [% date.format(ctx.parse_datetime(ctx.user.expire_date), DATE_FORMAT) %]
-
- [% IF ctx.expired_card %] - - - [% l("
Your library card has expired.
Please contact a librarian to resolve this issue.", fmt_expire_date) %] -
-
- [% END %] +
+ [% ctx.account_renew_message %]
-
+[%- END %] diff --git a/Open-ILS/src/templates-bootstrap/opac/renew-account.tt2 b/Open-ILS/src/templates-bootstrap/opac/renew-account.tt2 new file mode 100644 index 0000000000..0059a2865b --- /dev/null +++ b/Open-ILS/src/templates-bootstrap/opac/renew-account.tt2 @@ -0,0 +1,38 @@ +[%- PROCESS "opac/parts/header.tt2"; + PROCESS "opac/parts/org_selector.tt2"; + WRAPPER "opac/parts/base.tt2"; + INCLUDE "opac/parts/topnav.tt2"; + ctx.page_title = l("Renew Your Library Card"); +%] + +[% IF ctx.user %] + + + + +

[% l('Renew Your Library Card') %]

+ +
+
+
+

(English | Español)

+ + + +
+ + + + + +
+ +
+
+
+[% END %] +[%- END %]