Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability for patrons to opt into wage garnishment #488

Merged
merged 1 commit into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions app/controllers/lending_policy_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

class LendingPolicyController < ApplicationController
before_action :authenticate_user!

def accept
if patron.eligible_for_wage_garnishment?
client = SymphonyClient.new
client.accept_lending_policy(patron: patron, session_token: current_user.session_token)
end

# even if the patron is not eligible for wage garnishment (already opted in or some other reason),
# just redirect them to the thank you path to avoid further user confusion
redirect_to lending_policy_thank_you_path
end
end
14 changes: 14 additions & 0 deletions app/javascript/accept_lending_policy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const acceptLendingPolicy = () => {
const acceptLendingPolicyForm = document.getElementById('accept-lending-policy-form');

if (acceptLendingPolicyForm) {
const checkbox = document.getElementById('accept-lending-policy-checkbox');
const button = document.getElementById('accept-lending-policy-submit');

checkbox.addEventListener('change', (event) => {
button.disabled = !event.currentTarget.checked;
});
}
};

export default acceptLendingPolicy;
2 changes: 2 additions & 0 deletions app/javascript/packs/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'bootstrap/dist/js/bootstrap'
import './styles'

// Application javascript
import acceptLendingPolicy from '../accept_lending_policy'
import checkouts from "../view_checkouts"
import holds from "../view_holds"
import selectAll from "../select_all";
Expand All @@ -31,6 +32,7 @@ document.addEventListener("DOMContentLoaded", function() {
holds();
checkouts();
viewRequestedHolds();
acceptLendingPolicy();
});


Expand Down
29 changes: 29 additions & 0 deletions app/models/patron.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ class Patron
COLLECTION: 'The user has been sent to collection agency.'
}.with_indifferent_access

WAGE_GARNISHMENT_EXEMPT_LIBRARIES = [
'DSL-CARL',
'DSL-UP',
'HERSHEY'
].freeze

validates_presence_of :key

def initialize(record)
Expand Down Expand Up @@ -125,6 +131,24 @@ def address
}
end

def custom_information
fields['customInformation']
end

def eligible_for_wage_garnishment?
faculty_or_staff? && garnish_date == '00000000' && WAGE_GARNISHMENT_EXEMPT_LIBRARIES.exclude?(library)
end

def faculty_or_staff?
profile = fields.dig('profile', 'key')

['FACULTY', 'STAFF'].include?(profile)
end

def garnish_date
custom_field('GARNISH-DT')
end

private

def fields
Expand All @@ -142,4 +166,9 @@ def extract_address_data(address_field)
def standing_code
fields.dig('standing', 'key')
end

def custom_field(field_name)
field = custom_information.select { |k| k.dig('fields', 'code', 'key') == field_name }
field.first&.dig('fields', 'data')
end
end
34 changes: 33 additions & 1 deletion app/services/symphony_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def patron_info(patron_key:, session_token:, item_details: {})
params: {
includeFields: [
'*',
'customInformation{patronExtendedInformation{*}}',
*patron_linked_resources_fields(item_details)
].join(',')
})
Expand Down Expand Up @@ -147,6 +148,37 @@ def update_patron_info(patron:, params:, session_token:)
json: body
end

# Accepts the Libraries' lending policy/opts the user into wage garnishment.
# Changes the patron's standing from BARRED to OK and sets the garnish date to today's date.
def accept_lending_policy(patron:, session_token:)
new_garnish_date = DateTime.now.strftime('%Y%m%d')

custom_information = patron.custom_information

custom_information.select { |k| k.dig('fields', 'code', 'key') == 'GARNISH-DT' }
.map! { |k| k['fields']['data'] = new_garnish_date }

body = {
"resource": '/user/patron',
"key": patron.key,
"fields": {
"standing": {
"resource": '/policy/patronStanding',
"key": 'OK'
},
"customInformation": custom_information
}
}

authenticated_request "/user/patron/key/#{patron.key}",
headers: {
'x-sirs-sessionToken': session_token,
'SD-Prompt-Return': Settings.symws.additional_headers.sd_prompt_return
},
method: :put,
json: body
end

def get_hold_info(hold_key, session_token)
response_raw = hold_request hold_key, session_token

Expand Down Expand Up @@ -337,6 +369,6 @@ def base_url
end

def default_headers
DEFAULT_HEADERS.merge(Settings.symws.headers || {})
DEFAULT_HEADERS.merge(Settings.symws.default_headers || {})
end
end
30 changes: 30 additions & 0 deletions app/views/lending_policy/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<h1>Acceptance of University Libraries Lending Policy</h1>

<p>
The University Libraries has revised its Lending Policy to include the following statement:
</p>

<p class="mx-5">
"For lost materials, if payment is not received for replacement costs within 6 months from notification that the money is due at the Libraries, payment will be payroll deducted for current University employees. For all other fees, if payment is not received within 60 days, payment will be payroll deducted. Additional fees may be charged for this process."
</p>

<p>
The Lending Policy can be seen in its entirety on the Pennsylvania State University Libraries Web Page at <a href="https://libraries.psu.edu/services/borrow-renew/borrowing-privileges">https://libraries.psu.edu/services/borrow-renew/borrowing-privileges</a>. The policy is subject to change without individual notice. Any change in borrower status or address should be reported to the Libraries immediately.
</p>

<p>
By accepting these terms, the applicant agrees to the policies of the Pennsylvania State University Libraries. Please note that acceptance of this policy is necessary to maintain your borrowing privileges.
</p>

<hr class="my-4" />

<%= form_with url: lending_policy_accept_path, method: :post, id: 'accept-lending-policy-form', local: true do %>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="accept-lending-policy-checkbox">
<label class="form-check-label" for="accept-lending-policy-checkbox">
I authorize the Libraries, after appropriate notice, to deduct from my paycheck any fees incurred. Additional fees may be charged for this process. Upon acceptance of this policy, my borrowing privileges will be re-instated.
</label>
</div>

<%= submit_tag('Accept & Continue', class: 'btn btn-primary', id: 'accept-lending-policy-submit', disabled: true) %>
<% end %>
3 changes: 3 additions & 0 deletions app/views/lending_policy/thank_you.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<h1>Thank You</h1>

<p>Thank you for accepting the Libraries' Fee Policy.</p>
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
delete '/holds/batch', to: 'holds#batch_destroy', as: :holds_batch_destroy
patch '/checkouts/batch', to: 'checkouts#batch_update', as: :renewals_batch_update

get 'accept-lending-policy', to: 'lending_policy#show', as: :lending_policy_show
post 'accept-lending-policy', to: 'lending_policy#accept', as: :lending_policy_accept
get 'accept-lending-policy/thank-you', to: 'lending_policy#thank_you', as: :lending_policy_thank_you

get 'holds/all', to: 'holds#all', as: :holds_all
get 'holds/result', to: 'holds#result', as: :result
get 'checkouts/all', to: 'checkouts#all', as: :checkouts_all
Expand Down
4 changes: 3 additions & 1 deletion config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ symws:
url: <%= ENV.fetch("SYMWS_URL") { nil } %>
username: <%= ENV.fetch("SYMWS_USERNAME") { nil } %>
pin: "<%= ENV.fetch("SYMWS_PIN") { nil } %>"
headers:
default_headers:
sd_originating_app_id: cs
x_sirs_clientID: <%= ENV.fetch("X_SIRS_CLIENTID") { "PSUCATALOG" } %>
content_type: 'application/json'
accept: 'application/json'
additional_headers:
sd_prompt_return: <%= ENV.fetch("SYMWS_EDIT_OVERRIDE") { nil } %>
redis:
sidekiq:
uri: <%= ENV.fetch("REDIS_SIDEKIQ_URI") { "redis://127.0.0.1:6379/1" } %>
Expand Down
2 changes: 1 addition & 1 deletion config/settings/test.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
symws:
webaccess_url: https://example.com/webaccess
url: https://example.com/symwsbc
headers:
default_headers:
x_sirs_clientID: SymWSTestClient
login_params:
login: 'fake_user'
Expand Down
62 changes: 62 additions & 0 deletions spec/controllers/lending_policy_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe LendingPolicyController do
let(:mock_patron) { instance_double(Patron, eligible_for_wage_garnishment?: false) }
let(:mock_client) { instance_double(SymphonyClient, ping?: true) }

context 'with unauthenticated user' do
it 'goes to the application root' do
get(:show)
expect(response).to redirect_to root_url
end
end

context 'with an authenticated request' do
let(:user) {
instance_double(User,
username: 'zzz123',
name: 'Zeke',
patron_key: '1234567',
session_token: 'e0b5e1a3e86a399112b9eb893daeacfd')
}

before do
warden.set_user(user)
allow(controller).to receive(:patron).and_return(mock_patron)
allow(controller).to receive(:current_user).and_return(user)
allow(SymphonyClient).to receive(:new).and_return(mock_client)
allow(mock_client).to receive(:accept_lending_policy)
end

describe 'GET #show' do
it 'renders the show template' do
expect(get(:show)).to render_template 'show'
end
end

describe 'POST #accept' do
context 'when the patron is not eligible for wage garnishment' do
it 'renders the thank you template' do
expect(post(:accept)).to redirect_to lending_policy_thank_you_path
end
end

context 'when the patron is eligible for wage garnishment' do
let(:mock_patron) { instance_double(Patron, eligible_for_wage_garnishment?: true) }

it 'accepts the lending policy' do
post(:accept)

expect(mock_client).to have_received(:accept_lending_policy)
.with(patron: mock_patron, session_token: user.session_token)
end

it 'renders the thank you template' do
expect(post(:accept)).to redirect_to lending_policy_thank_you_path
end
end
end
end
end
36 changes: 36 additions & 0 deletions spec/features/lending_policy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Lending Policy', type: :feature do
let(:mock_user) { 'patron1' }

before do
login_permanently_as username: 'PATRON1', patron_key: mock_user
visit lending_policy_show_path
end

after do
Warden::Manager._on_request.clear
Redis.current.flushall
end

describe 'when the user clicks the checkbox', js: true do
it 'toggles the enabled state of the button' do
expect(page).to have_button('Accept & Continue', disabled: true)

page.check 'accept-lending-policy-checkbox'

expect(page).to have_button('Accept & Continue', disabled: false)
end
end

describe 'when the user submits the form', js: true do
it 'redirects the user to the thank you page' do
page.check 'accept-lending-policy-checkbox'
page.click_button 'Accept & Continue'

expect(page).to have_current_path lending_policy_thank_you_path, ignore_query: true
end
end
end
4 changes: 3 additions & 1 deletion spec/jobs/view_checkouts_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@

context 'when SymphonyClient does not respond with 200/OK' do
before do
stub_request(:get, 'https://example.com/symwsbc/user/patron/key/patron2?includeFields=*,circRecordList%7B*,bib%7Btitle,author,callList%7B*%7D%7D,item%7B*,bib%7Bshadowed,title,author%7D,call%7BsortCallNumber,dispCallNumber%7D%7D%7D')
stub_request(:get, 'https://example.com/symwsbc/user/patron/key/patron2?includeFields=*,'\
'customInformation{patronExtendedInformation{*}},circRecordList%7B*,bib%7Btitle,author,callList%7B*%7D%7D,'\
'item%7B*,bib%7Bshadowed,title,author%7D,call%7BsortCallNumber,dispCallNumber%7D%7D%7D')
.to_return(status: 500, body: '{ "messageList": [{ "message": "A bad thing happened" }] }', headers: {})
end

Expand Down
4 changes: 3 additions & 1 deletion spec/jobs/view_holds_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@

context 'when SymphonyClient does not respond with 200/OK' do
before do
stub_request(:get, 'https://example.com/symwsbc/user/patron/key/patron1?includeFields=*,holdRecordList%7B*,bib%7Btitle,author,callList%7B*%7D%7D,item%7B*,bib%7Bshadowed,title,author%7D,call%7BsortCallNumber,dispCallNumber%7D%7D%7D')
stub_request(:get, 'https://example.com/symwsbc/user/patron/key/patron1?includeFields=*,'\
'customInformation{patronExtendedInformation{*}},holdRecordList%7B*,bib%7Btitle,author,callList%7B*%7D%7D,'\
'item%7B*,bib%7Bshadowed,title,author%7D,call%7BsortCallNumber,dispCallNumber%7D%7D%7D')
.to_return(status: 500, body: '{ "messageList": [{ "message": "A bad thing happened" }] }', headers: {})
end

Expand Down
Loading