LP1902937: Quipu Online Account Renewal
authorTerran McCanna <tmccanna@georgialibraries.org>
Tue, 27 Dec 2022 18:05:33 +0000 (13:05 -0500)
committerJason Stephenson <jason@sigio.com>
Thu, 6 Apr 2023 18:32:30 +0000 (14:32 -0400)
Online Renewal - Squashed from several working branch commits.

- 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 permission group allow e-renewal
* patron has a valid billing address
* patron has a valid day phone

Add 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.

Add Standing Penalty for temporary renewal.

Add temporay renewal flag to Quipu response.

Create new API instead of piggybacking on vital_stats:
open-ils.actor.user.opac.renewal

Add column to permission.grp_tree to allow e-renew.

See & update permission group setting through staff client.

Look up permission groups by name or by e-renewal eligibility flag.

Commit message edited by Jason Stephenson - 20230127

Signed-off-by: Terran McCanna <tmccanna@georgialibraries.org>
Signed-off-by: Chris Sharp <csharp@georgialibraries.org>
Signed-off-by: Jason Stephenson <jason@sigio.com>
15 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.html
Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/permission.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Ecard.pm
Open-ILS/src/sql/Pg/006.schema.permissions.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX-quipu-standing_penalty.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.erenew_column_pgt.sql [new file with mode: 0644]
Open-ILS/src/templates-bootstrap/opac/myopac/main.tt2
Open-ILS/src/templates-bootstrap/opac/myopac/prefs.tt2
Open-ILS/src/templates-bootstrap/opac/renew-account-sp.tt2 [new file with mode: 0644]
Open-ILS/src/templates-bootstrap/opac/renew-account.tt2 [new file with mode: 0644]

index 63353a3..fae73e2 100644 (file)
@@ -8399,6 +8399,7 @@ SELECT  usr,
                        <field reporter:label="Required Permission" name="application_perm" reporter:datatype="text"/>
                        <field reporter:label="Is User Group" name="usergroup" reporter:datatype="bool"/>
                        <field reporter:label="Hold Priority" name="hold_priority" reporter:datatype="int"/>
+                       <field reporter:label="E-renew?" name="erenew" reporter:datatype="bool"/>
                </fields>
                <links>
                        <link field="parent" reltype="has_a" key="id" map="" class="pgt"/>
index 02d045f..9de9781 100644 (file)
                 {{selected.callerData.usergroup() === 't'}}
               </div>
             </div>
+            <div class="row">
+              <div class="col-lg-4">
+                <label i18n>Online Account Renewal Allowed?: </label>
+              </div>
+              <div class="col-lg-8 font-weight-bold">
+                {{selected.callerData.erenew() == 't'}}
+              </div>
+            </div>
           </ng-template>
         </li>
         <li role="presentation" [ngbNavItem]="'perm'">
index da0c9d7..577034f 100644 (file)
@@ -623,8 +623,12 @@ sub update_patron {
     ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
     return $evt if $evt;
 
-    ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
-    return $evt if $evt;
+    # PINES - modified this because it was breaking e-renew, could not verify that it actually
+    # worked anywhere because the subroutine looks like it's trying to update the wrong table - TM
+    if($patron->isnew()) {
+        ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
+        return $evt if $evt;
+    }
 
     $evt = apply_invalid_addr_penalty($e, $patron);
     return $evt if $evt;
@@ -2064,6 +2068,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 = [
index 7c7cab6..af9ada5 100644 (file)
@@ -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/;
index 11eff6a..49bcd63 100644 (file)
@@ -187,8 +187,11 @@ sub load {
     $self->load_simple("myopac") if $path =~ m:opac/myopac:; # A default page for myopac parts
 
     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?
index 78bba4a..720387c 100644 (file)
@@ -37,7 +37,7 @@ sub prepare_extended_user_info {
         {
             flesh => 2,
             flesh_fields => {
-                au => [qw/card home_ou addresses ident_type locale billing_address waiver_entries stat_cat_entries/, @extra_flesh],
+                au => [qw/card home_ou addresses ident_type locale billing_address mailing_address waiver_entries stat_cat_entries/, @extra_flesh],
                 "aou" => ["billing_address"],
                 "actscecm" => ["stat_cat"]
             }
@@ -120,6 +120,20 @@ 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 ($user->billing_address and $user->billing_address->valid and $user->billing_address->valid eq 't') {
+        $self->ctx->{valid_billing_address} = 't';
+    } else {
+        $self->ctx->{valid_billing_address} = 'f';
+    }
+    if ($user->mailing_address and $user->mailing_address->valid and $user->mailing_address->valid eq 't') {
+        $self->ctx->{valid_mailing_address} = 't';
+    } else {
+        $self->ctx->{valid_mailing_address} = 'f';
+    }
+
+    $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 +203,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 +2658,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 +3566,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} = '<div style="border:2px solid green;padding:5px;">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.</div>';
+    } elsif (DateTime->today->add(days=>29) 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} = '<div style="border:2px solid green;padding:5px;">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.</div>';
+    } elsif ($ctx->{user_stats}->{fines}->{balance_owed} gt 0) { #user has fines
+        $ctx->{account_renew_message} = '<div style="border:2px solid green;padding:5px;">Your account
+        is due for renewal. Please pay your outstanding fines in order to renew your account.</div>';
+    } else {
+        $ctx->{account_renew_message} = '<span class="light_border"><a class="btn btn-sm btn-action"
+        href="/eg/opac/renew-account"><i class="fas fa-user-cog"></i>Click here to renew your account</a></span>';
+        $cache->put_cache('account_renew_ok','true',3600);
+    }
+
+    return 1;
+}
+
 1;
index 1b16c94..21af15c 100644 (file)
@@ -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},
@@ -189,7 +222,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} 
@@ -200,6 +233,7 @@ sub handle_testmode_api {
 
     $ctx->{response}->{messages} = [fields => \@doc_fields];
     $ctx->{response}->{status} = 'API_OK';
+
     return $self->compile_response;
 }
 
@@ -219,6 +253,7 @@ sub handle_datamode_api {
     }
 
     $ctx->{response}->{status} = 'DATA_OK';
+
     return $self->compile_response;
 }
 
@@ -227,6 +262,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') || '';
@@ -254,28 +306,54 @@ 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;
-    # Add survey responses commented out because it is not universal.
-    # We should come up with a way to configure it before uncommenting
-    # it globally.
-    #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};
-
-    # The code below does nothing in a stock Evergreen installation.
-    # It is included in case a site wishes to set up action trigger
-    # events to do some additional verification or notification for
-    # patrons who have signed up for an eCard.
-    $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;
-    $ctx->{response}->{expiration_date} = substr($ctx->{user}->expire_date, 0, 10);
+
+    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;
+        $ctx->{response}->{expiration_date} = substr($ctx->{user}->expire_date, 0, 10);
+       }
 
     return $self->compile_response;
 }
@@ -327,28 +405,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;
@@ -358,6 +426,8 @@ 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(
@@ -373,7 +443,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';
 
@@ -390,6 +460,7 @@ sub make_user {
         }
 
         $self->verify_dob($val) if $field eq 'dob' && $val;
+
         $au->$field($val);
     }
 
@@ -397,6 +468,85 @@ 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;
     my $ctx = $self->ctx;
@@ -494,16 +644,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;
@@ -531,7 +671,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_/;
@@ -565,6 +705,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;
+}
+
 # TODO: The code in add_usr_settings is totally arbitrary and should
 # be modified to look up settings in the database.
 sub add_usr_settings {
@@ -595,9 +819,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);
 
@@ -605,35 +830,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;
@@ -717,6 +913,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',
@@ -727,11 +924,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;
@@ -741,5 +945,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;
 
index 05d38a6..c1f1622 100644 (file)
@@ -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);
 
index 1aba18c..cc293c2 100644 (file)
@@ -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 (file)
index 0000000..e7d82fd
--- /dev/null
@@ -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 (file)
index 0000000..cf58401
--- /dev/null
@@ -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;
index 87dae3f..841201b 100755 (executable)
                             [% date.format(ctx.parse_datetime(ctx.user.expire_date), DATE_FORMAT) %]
                         </span>
                     </div>
-                    <div class="col-12">
-                     [% IF ctx.expired_card %]
-                        <span>
-                            <em>
-                            [% l("<br>Your library card has expired.<br>Please contact a librarian to resolve this issue.", fmt_expire_date) %]
-                            </em>
-                        </span>
-                        [% END %]
+                    <div class="col-12" style="padding-bottom:20px;padding-top:20px;">
+                        [% ctx.account_renew_message %]
                     </div>
-                    <br>
                     <div class="col-12">
                      <a href="[% mkurl(ctx.opac_root _ '/myopac/circs') %]"
                             title="[% l('View My Checked Out Items') %]">
index 684fcbb..49c1f99 100755 (executable)
                     [% ELSE %]
                         [% date.format(ctx.parse_datetime(ctx.user.expire_date), DATE_FORMAT) %]
                     [% END %]
+                    <div class="col-12" style="padding:20px 0px 20px 0px;">
+                        [% ctx.account_renew_message %]
+                    </div>
                 </td>
                 <td></td>
             </tr>
diff --git a/Open-ILS/src/templates-bootstrap/opac/renew-account-sp.tt2 b/Open-ILS/src/templates-bootstrap/opac/renew-account-sp.tt2
new file mode 100644 (file)
index 0000000..ec5e051
--- /dev/null
@@ -0,0 +1,35 @@
+[%- 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");
+%]
+
+<!-- eCARD scripts here -->
+<script id="eRenewServer" src="https://ecard.quipugroup.net/js/eRenewEmbed.js"></script>
+<script>loadQGeRenew(48)</script>
+
+<h2 class="sr-only">[% l('Renew Your Library Card') %]</h2>
+<div id="content-wrapper">
+    <div id="main-content-register">
+        <div class="common-full-pad"></div>
+            <p style="font-size:.8em;">(<a href="/eg/opac/renew-account">English</a> | Español)</p>
+            
+            <!-- eCARD div here: -->
+            <input type="hidden" id="patronID" value="[% ctx.user.id %]" />
+            <div id="eRenew" data-language="sp" data-branchid=""></div>
+
+            
+            <!-- eCARD requires JavaScript in order to display the registration form -->
+            <!-- The following will detect if JavaScript is enabled on the patron's browser -->
+            <noscript>
+            <h2 style="color:red;">Warning - JavaScript Required</h2>
+            <p>For full functionality of this web page it is necessary to enable JavaScript in your browser. For more information on most browsers, try <a href="http://www.enable-javascript.com/" target="_blank">How to enable JavaScript</a> OR <a href="http://activatejavascript.org/en/instructions" target="_blank">activatejavascript.org</a><
+            </p>
+            </noscript>
+            </div>
+        
+        <div class="common-full-pad"></div>    
+    </div>
+</div>
+[%- 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 (file)
index 0000000..0059a28
--- /dev/null
@@ -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 %]
+<!-- eCARD scripts here -->
+<script id="eRenewServer" src="https://ecard.quipugroup.net/js/eRenewEmbed.js"></script>
+<script>loadQGeRenew(48)</script>
+
+<h2 class="sr-only">[% l('Renew Your Library Card') %]</h2>
+
+<div id="content-wrapper">
+    <div id="main-content-register">
+        <div class="common-full-pad"></div>
+            <p style="font-size:.8em;">(English | <a href="/eg/opac/renew-account-sp">Español</a>)</p>
+            
+            <!-- eCARD div here: -->
+            <input type="hidden" id="patronID" value="[% ctx.user.id %]" />
+            <div id="eRenew" data-language="en" data-branchid=""></div>
+
+            
+            <!-- eCARD requires JavaScript in order to display the registration form -->
+            <!-- The following will detect if JavaScript is enabled on the patron's browser -->
+            <noscript>
+            <h2 style="color:red;">Warning - JavaScript Required</h2>
+            <p>For full functionality of this web page it is necessary to enable JavaScript in your browser. For more information on most browsers, try <a href="http://www.enable-javascript.com/" target="_blank">How to enable JavaScript</a> OR <a href="http://activatejavascript.org/en/instructions" target="_blank">activatejavascript.org</a><
+            </p>
+            </noscript>
+            </div>
+        
+        <div class="common-full-pad"></div>    
+    </div>
+</div>
+[% END %]
+[%- END %]