Lp 1861319: Add Release Notes and Perl Live Tests
authorJason Stephenson <jason@sigio.com>
Sat, 30 Jan 2021 00:36:19 +0000 (19:36 -0500)
committerGalen Charlton <gmc@equinoxinitiative.org>
Wed, 10 Mar 2021 19:59:41 +0000 (14:59 -0500)
Signed-off-by: Jason Stephenson <jason@sigio.com>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Open-ILS/src/perlmods/live_t/32-lp1861319-circ-renewal-expired-patrons.t [new file with mode: 0755]
docs/RELEASE_NOTES_NEXT/Circulation/renewal_for_expired_patrons.adoc [new file with mode: 0644]

diff --git a/Open-ILS/src/perlmods/live_t/32-lp1861319-circ-renewal-expired-patrons.t b/Open-ILS/src/perlmods/live_t/32-lp1861319-circ-renewal-expired-patrons.t
new file mode 100755 (executable)
index 0000000..64e6b18
--- /dev/null
@@ -0,0 +1,368 @@
+#!perl
+use strict; use warnings;
+use Test::More tests => 18;
+use OpenILS::Utils::TestUtils;
+use OpenILS::Const qw(:const);
+use OpenILS::Utils::DateTime;
+use DateTime;
+use DateTime::TimeZone;
+use DateTime::Format::ISO8601;
+
+# We need the local timezone for later.
+my $localTZ = DateTime::TimeZone->new(name => 'local');
+
+my $script = OpenILS::Utils::TestUtils->new();
+my $U = 'OpenILS::Application::AppUtils';
+
+diag("Test LP 1861319 Allow Circulation Renewal for Expired Patrons");
+
+use constant {
+    BR1_ID => 4,
+    BR2_ID => 6,
+    BR1_WORKSTATION => 'BR1-lp1861319-test-renewal',
+    BR2_WORKSTATION => 'BR2-lp1861319-test renewal',
+    ADMIN_USER => 'admin',
+    ADMIN_PASS => 'demo123'
+};
+
+# Login
+$script->authenticate({
+    username => ADMIN_USER,
+    password => ADMIN_PASS,
+    type => 'staff'
+});
+BAIL_OUT('Failed to Login') unless ($script->authtoken);
+
+# Register BR1 workstation.
+my $ws = $script->find_or_register_workstation(BR1_WORKSTATION, BR1_ID);
+BAIL_OUT("Failed to register " . BR1_WORKSTATION) unless($ws);
+
+# Logout
+$script->logout();
+
+# Login with BR1 Workstation
+$script->authenticate({
+    username => ADMIN_USER,
+    password => ADMIN_PASS,
+    type => 'staff',
+    workstation => BR1_WORKSTATION
+});
+BAIL_OUT('Failed to login with ' . BR1_WORKSTATION) unless($script->authtoken);
+
+# Get a cstore editor for later use.
+my $editor = $script->editor(authtoken=>$script->authtoken);
+
+# Check that the circ.renew.expired_patron_allow setting constant is defined
+ok(defined(OILS_SETTING_ALLOW_RENEW_FOR_EXPIRED_PATRON),
+   'OILS_SETTING_ALLOW_RENEW_FOR_EXPIRED_PATRON constant exists');
+
+# Check that the circ.renew.expired_patron_allow setting exists in the database
+my $setting = $editor->search_config_org_unit_setting_type(
+    {name => OILS_SETTING_ALLOW_RENEW_FOR_EXPIRED_PATRON}
+);
+# Get the first/only one:
+$setting = (defined($setting)) ? $setting->[0] : $setting;
+ok(defined($setting), OILS_SETTING_ALLOW_RENEW_FOR_EXPIRED_PATRON . ' setting exists in database');
+
+# Find a circulation with renewals remaining
+my $circ = $editor->search_action_circulation([
+    {
+        circ_lib => BR1_ID,
+        checkin_time => undef,
+        renewal_remaining => {'>' => 0}
+    },
+    {limit => 1}
+]);
+$circ = defined($circ) ? $circ->[0] : $circ;
+isa_ok($circ, 'Fieldmapper::action::circulation', 'Found open circulation at BR1');
+
+# Get the circ patron information.
+my $patron = $editor->retrieve_actor_user($circ->usr);
+isa_ok($patron, 'Fieldmapper::actor::user', 'Found circulation user');
+
+# Expire the patron if they are not already expired.
+my ($saved_expire_date);
+SKIP: {
+    if (check_usr_expired($patron)) {
+        skip 'Patron already expired', 1;
+    } else {
+        $saved_expire_date = $patron->expire_date;
+        $patron->expire_date(DateTime->now()->set_time_zone($localTZ)->subtract(days => 1)->strftime('%FT%T%z'));
+        $editor->xact_begin;
+        if ($editor->update_actor_user($patron)) {
+            $editor->xact_commit;
+        } else {
+            $editor->xact_rollback;
+            BAIL_OUT("Patron update failed");
+        }
+        $patron = $editor->retrieve_actor_user($patron->id);
+        ok(check_usr_expired($patron), 'Patron set to expired.');
+    }
+}
+
+# Attempt a renewal. It should fail.
+my $renew_params = {
+    copy_id => $circ->target_copy,
+    patron_id => $circ->usr,
+    desk_renewal => 1
+};
+my $result = $U->simplereq(
+    'open-ils.circ',
+    'open-ils.circ.renew.override',
+    $script->authtoken,
+    $renew_params
+);
+if (ref($result) eq 'ARRAY') {
+    $result = $result->[0];
+}
+is($result->{textcode}, 'PATRON_ACCOUNT_EXPIRED', 'Renewal failed: ' . $result->{textcode});
+
+# Set the circ.renew.expired_patron_allow setting at BR1
+$setting = Fieldmapper::actor::org_unit_setting->new;
+$setting->name(OILS_SETTING_ALLOW_RENEW_FOR_EXPIRED_PATRON);
+$setting->org_unit(BR1_ID);
+$setting->value('true');
+$editor->xact_begin;
+$result = $editor->create_actor_org_unit_setting($setting);
+if ($result) {
+    $editor->xact_commit;
+    ok($result, 'Set ' . OILS_SETTING_ALLOW_RENEW_FOR_EXPIRED_PATRON . ' for BR1');
+} else {
+    $editor->xact_rollback;
+    BAIL_OUT("Failed to set " . OILS_SETTING_ALLOW_RENEW_FOR_EXPIRED_PATRON . ' for BR1');
+}
+
+# Attempt the renewal again, expect success.
+$result = $U->simplereq(
+    'open-ils.circ',
+    'open-ils.circ.renew.override',
+    $script->authtoken,
+    $renew_params
+);
+if (ref($result) eq 'ARRAY') {
+    $result = $result->[0];
+}
+isnt($result->{textcode}, 'PATRON_ACCOUNT_EXPIRED', 'Renewal Result: ' . $result->{textcode});
+
+# Find a circulating copy at BR1 that is not checked out.
+my $copy = $editor->search_asset_copy([
+    {
+        circ_lib => BR1_ID,
+        status => OILS_COPY_STATUS_AVAILABLE,
+        circulate => 't'
+    },
+    {limit => 1}
+]);
+$copy = defined($copy) ? $copy->[0] : $copy;
+isa_ok($copy, 'Fieldmapper::asset::copy', 'Found copy at BR1');
+
+# Check it out, expect failure
+my $checkout_params = {
+    copy_id => $copy->id,
+    patron_id => $patron->id
+};
+$result = $U->simplereq(
+    'open-ils.circ',
+    'open-ils.circ.checkout.full.override',
+    $script->authtoken,
+    $checkout_params
+);
+if (ref($result) eq 'ARRAY') {
+    $result = $result->[0];
+}
+is($result->{textcode}, 'PATRON_ACCOUNT_EXPIRED', 'Checkout failed: ' . $result->{textcode});
+
+# Reset the patron expire_date
+if (defined($saved_expire_date)) {
+    $patron->expire_date($saved_expire_date);
+    $editor->xact_begin;
+    if ($editor->update_actor_user($patron)) {
+        $editor->xact_commit;
+    } else {
+        $editor->xact_rollback;
+    }
+    undef($saved_expire_date);
+}
+
+# Logout
+$script->logout();
+
+# Destroy our editor.
+undef($editor);
+
+# Login
+$script->authenticate({
+    username => ADMIN_USER,
+    password => ADMIN_PASS,
+    type => 'staff'
+});
+BAIL_OUT('Failed to Login') unless ($script->authtoken);
+
+# Register BR2 workstation.
+$ws = $script->find_or_register_workstation(BR2_WORKSTATION, BR2_ID);
+BAIL_OUT("Failed to register " . BR2_WORKSTATION) unless($ws);
+
+# Logout
+$script->logout();
+
+# Login with BR2 Workstation
+$script->authenticate({
+    username => ADMIN_USER,
+    password => ADMIN_PASS,
+    type => 'staff',
+    workstation => BR2_WORKSTATION
+});
+BAIL_OUT('Failed to login with ' . BR2_WORKSTATION) unless($script->authtoken);
+
+# Get a new editor with our authtoken.
+$editor = $script->editor(authtoken=>$script->authtoken);
+
+# Find a circulation with renewals remaining at BR2
+$circ = $editor->search_action_circulation([
+    {
+        circ_lib => BR2_ID,
+        checkin_time => undef,
+        renewal_remaining => {'>' => 0}
+    },
+    {limit => 1}
+]);
+$circ = defined($circ) ? $circ->[0] : $circ;
+isa_ok($circ, 'Fieldmapper::action::circulation', 'Found open circulation at BR2');
+
+# Get the circ patron information.
+$patron = $editor->retrieve_actor_user($circ->usr);
+isa_ok($patron, 'Fieldmapper::actor::user', 'Found circulation user');
+
+# Expire the patron if they are not already expired.
+SKIP: {
+    if (check_usr_expired($patron)) {
+        skip 'Patron already expired', 1;
+    } else {
+        $saved_expire_date = $patron->expire_date;
+        $patron->expire_date(DateTime->now()->set_time_zone($localTZ)->subtract(days => 1)->strftime('%FT%T%z'));
+        $editor->xact_begin;
+        if ($editor->update_actor_user($patron)) {
+            $editor->xact_commit;
+            $patron = $editor->retrieve_actor_user($patron->id);
+            ok(check_usr_expired($patron), 'Patron set to expired.');
+        } else {
+            $editor->xact_rollback;
+            BAIL_OUT("Patron update failed");
+        }
+    }
+}
+
+# Attempt a renewal. It should fail.
+$renew_params = {
+    copy_id => $circ->target_copy,
+    patron_id => $circ->usr,
+    desk_renewal => 1
+};
+$result = $U->simplereq(
+    'open-ils.circ',
+    'open-ils.circ.renew.override',
+    $script->authtoken,
+    $renew_params
+);
+if (ref($result) eq 'ARRAY') {
+    $result = $result->[0];
+}
+is($result->{textcode}, 'PATRON_ACCOUNT_EXPIRED', 'Renewal failed: ' . $result->{textcode});
+
+# Reset the patron expire_date
+if (defined($saved_expire_date)) {
+    $patron->expire_date($saved_expire_date);
+    $editor->xact_begin;
+    if ($editor->update_actor_user($patron)) {
+        $editor->xact_commit;
+        ok(!check_usr_expired($patron), 'Patron set to not expired');
+    } else {
+        $editor->xact_rollback;
+        BAIL_OUT('Patron expire date reset failed');
+    }
+    undef($saved_expire_date);
+} else {
+    # Set patron expire date to 30 days in the future.
+    $saved_expire_date = $patron->expire_date;
+    $patron->expire_date(DateTime->now()->set_time_zone($localTZ)->add(days => 30)->strftime('%FT%T%z'));
+    $editor->xact_begin;
+    if ($editor->update_actor_user($patron)) {
+        $editor->xact_commit;
+        $patron = $editor->retrieve_actor_user($patron->id);
+        ok(!check_usr_expired($patron), 'Patron is not expired.');
+    } else {
+        $editor->xact_rollback;
+        BAIL_OUT("Patron update failed");
+    }
+}
+
+# Attempt renewal, expect success.
+$result = $U->simplereq(
+    'open-ils.circ',
+    'open-ils.circ.renew.override',
+    $script->authtoken,
+    $renew_params
+);
+if (ref($result) eq 'ARRAY') {
+    $result = $result->[0];
+}
+isnt($result->{textcode}, 'PATRON_ACCOUNT_EXPIRED', 'Renewal Result: ' . $result->{textcode});
+
+# Find a circulating copy at BR2 that is not checked out.
+$copy = $editor->search_asset_copy([
+    {
+        circ_lib => BR2_ID,
+        status => OILS_COPY_STATUS_AVAILABLE,
+        circulate => 't'
+    },
+    {limit => 1}
+]);
+$copy = defined($copy) ? $copy->[0] : $copy;
+isa_ok($copy, 'Fieldmapper::asset::copy', 'Found copy at BR2');
+
+# Check it out, expect failure
+$checkout_params = {
+    copy_id => $copy->id,
+    patron_id => $patron->id
+};
+$result = $U->simplereq(
+    'open-ils.circ',
+    'open-ils.circ.checkout.full.override',
+    $script->authtoken,
+    $checkout_params
+);
+if (ref($result) eq 'ARRAY') {
+    $result = $result->[0];
+}
+isnt($result->{textcode}, 'PATRON_ACCOUNT_EXPIRED', 'Checkout result: ' . $result->{textcode});
+
+# Reset the patron expire_date if necessary
+if (defined($saved_expire_date)) {
+    $patron->expire_date($saved_expire_date);
+    $editor->xact_begin;
+    if ($editor->update_actor_user($patron)) {
+        $editor->xact_commit;
+    } else {
+        $editor->xact_rollback;
+    }
+    undef($saved_expire_date);
+}
+
+# Delete the setting from actor.org_unit_setting
+$editor->xact_begin;
+if ($editor->delete_actor_org_unit_setting($setting)) {
+    $editor->commit; # Commit so that we disconnect.
+} else {
+    $editor->rollback;
+}
+
+# Logout
+$script->logout(); # Not a test, just to be pedantic.
+
+# Utiity functions
+sub check_usr_expired {
+    my ($user) = @_;
+    my $expire = DateTime::Format::ISO8601->new->parse_datetime(
+        OpenILS::Utils::DateTime->clean_ISO8601($user->expire_date));
+    return (time > $expire->epoch);
+}
diff --git a/docs/RELEASE_NOTES_NEXT/Circulation/renewal_for_expired_patrons.adoc b/docs/RELEASE_NOTES_NEXT/Circulation/renewal_for_expired_patrons.adoc
new file mode 100644 (file)
index 0000000..8281b2e
--- /dev/null
@@ -0,0 +1,15 @@
+Allow Circulation Renewal for Expired Patrons
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+The "Allow renewal request if renewal recipient privileges have
+expired" organizational unit setting can be set to true to permit
+expired patrons to renew circulations.  Allowing renewals for expired
+patrons reduces the number of auto-renewal failures and assumes that a
+patron with items out eligible for renewals has not been expired for
+very long and that such patrons are likely to renew their privileges
+in a timely manner.
+
+The setting is referenced based on the current circulation library for
+the renewal.  It takes into account the global flags for "Circ: Use
+original circulation library on desk renewal instead of the
+workstation library" and "Circ: Use original circulation library on
+opac renewal instead of user home library."