Skip to content
Open
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
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ gem 'puma', '~> 4.1'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

gem 'faker', '~> 2.10.1'

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'

Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ GEM
concurrent-ruby (1.1.8)
crass (1.0.6)
erubi (1.10.0)
faker (2.10.2)
i18n (>= 1.6, < 2)
ffi (1.15.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
Expand Down Expand Up @@ -149,6 +151,7 @@ PLATFORMS
DEPENDENCIES
bootsnap (>= 1.4.2)
byebug
faker (~> 2.10.1)
listen (~> 3.2)
pg (~> 1.2.2)
puma (~> 4.1)
Expand Down
30 changes: 26 additions & 4 deletions app/controllers/debt_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class DebtController < ApplicationController
before_action :load_borrower
before_action :load_debt, except: [:index, :create]

def index
render json: { debts: @borrower.debts }, status: :ok
Expand All @@ -24,12 +25,25 @@ def create
end

def show
@debt = @borrower.debts.find_by(id: params[:id])
render json: { debt: @debt }, status: :ok
end

if @debt
def autofill
begin
@debt.autofill!
render json: { debt: @debt }, status: :ok
else
render json: { error: "Debt not found" }, status: :not_found
rescue Debt::DebtServiceError => e
render json: { errors: e }, status: :service_unavailable
end
end

def payoff_amount
num_days_before_payoff = params[:num_days_before_payoff] || Debt::DEFAULT_NUM_DAYS_BEFORE_PAYOFF

begin
render json: { payoff_amount: @debt.payoff_amount(num_days_before_payoff) }, status: :ok
rescue ArgumentError => e
render json: { errors: e }, status: :internal_server_error
end
end

Expand All @@ -42,4 +56,12 @@ def load_borrower
render json: {error: "Borrower not found" }, status: :not_found
end
end

def load_debt
@debt = @borrower.debts.find_by(id: params[:debt_id] || params[:id])

if @debt.nil?
render json: {error: "Debt not found" }, status: :not_found
end
end
end
40 changes: 39 additions & 1 deletion app/models/debt.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
class Debt < ApplicationRecord

class DebtServiceError < StandardError; end

DEFAULT_NUM_DAYS_BEFORE_PAYOFF = 30.freeze

belongs_to :borrower

validates :borrower, :account_number, :description, :status, presence: true
validates :borrower, :account_number, :description, :status, presence: true

def autofill!
raise DebtServiceError, "Service credentials unspecified" if ENV["SERVICE_CREDENTIALS"].nil?

# BEGIN HAND-WAVING
# For the purpose of this examination, assume that the following is making a call to retrieve these values from on-premise mainframe.

self.current_balance = Faker::Number.decimal(l_digits: 5, r_digits: 2)
self.interest_rate = Faker::Number.decimal(l_digits: 1, r_digits: 3)
self.current_balance = Faker::Number.decimal(l_digits: 5, r_digits: 2)
self.outstanding_interest_amount = Faker::Number.decimal(l_digits: 4, r_digits: 2)
self.servicer_address = Faker::Address.full_address
self.last_payment_date = Faker::Date.backward
self.last_payment_amount = Faker::Number.decimal(l_digits: 3, r_digits: 2)
self.last_statement_balance = Faker::Number.decimal(l_digits: 3, r_digits: 2)
self.last_statement_issue_date = Faker::Date.backward
self.next_payment_due_date = Faker::Date.forward

# END HAND-WAVING

nil
end

def payoff_amount(num_days_before_payoff = DEFAULT_NUM_DAYS_BEFORE_PAYOFF)
raise ArgumentError, "num_days_before_payoff must be an integer" unless num_days_before_payoff.is_a? Integer
raise ArgumentError, "num_days_before_payoff must be positive" if num_days_before_payoff < 0

daily_interest_rate = (self.outstanding_interest_amount / 100) / 365
daily_interest_amount = self.current_balance * daily_interest_rate
accumulated_interest_before_payoff = daily_interest_amount * num_days_before_payoff

self.current_balance + accumulated_interest_before_payoff
end
end
5 changes: 4 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
Rails.application.routes.draw do
resources :borrower, only: [:create, :show] do
resources :debt, only: [:create, :show, :index]
resources :debt, only: [:create, :show, :index] do
put :autofill
get :payoff_amount
end
end
end
19 changes: 19 additions & 0 deletions test/controllers/debt_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,23 @@ class DebtControllerTest < ActionDispatch::IntegrationTest
assert_equal debt.as_json, JSON.parse(@response.body)["debt"]
end

test "autofill" do
if ENV["SERVICE_CREDENTIALS"].nil?
skip("Service credentials not set. Skipping")
end

debt = debts.last

put borrower_debt_autofill_url(borrower_id: debt.borrower.id, debt_id: debt.id), as: :json
assert_response :ok
end

test "payoff_amount" do
debt = debts.last

get borrower_debt_payoff_amount_url(borrower_id: debt.borrower.id, debt_id: debt.id), as: :json

assert_response :ok
assert_equal debt.payoff_amount.as_json, JSON.parse(@response.body)["payoff_amount"]
end
end
50 changes: 50 additions & 0 deletions test/models/debt_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,54 @@ class DebtTest < ActiveSupport::TestCase
assert_includes debt.errors.messages.keys, attribute
end
end

test "autofill!" do
if ENV["SERVICE_CREDENTIALS"].nil?
skip("Service credentials not set. Skipping")
end

debt = Debt.new(
account_number: Faker::Number.number(digits: 10),
description: Faker::Lorem.sentence,
status: "Open"
)
debt.borrower = Borrower.find(borrowers.first.id)
debt.autofill!

assert debt.valid?
assert debt.save
end

test "payoff_amount" do
debt = debts.first

(0..90).each do |num_days_before_payoff|
daily_interest_rate = (debt.outstanding_interest_amount / 100) / 365
daily_interest_amount = debt.current_balance * daily_interest_rate
accumulated_interest_before_payoff = daily_interest_amount * num_days_before_payoff

assert_equal(
(debt.current_balance + accumulated_interest_before_payoff),
debt.payoff_amount(num_days_before_payoff)
)
end
end

test "payoff_amount defaults to #{Debt::DEFAULT_NUM_DAYS_BEFORE_PAYOFF} days" do
debt = debts.first

daily_interest_rate = (debt.outstanding_interest_amount / 100) / 365
daily_interest_amount = debt.current_balance * daily_interest_rate
accumulated_interest_before_payoff = daily_interest_amount * Debt::DEFAULT_NUM_DAYS_BEFORE_PAYOFF

assert_equal (debt.current_balance + accumulated_interest_before_payoff), debt.payoff_amount
end

test "payoff_amount throws error with negative parameter" do
assert_raises(ArgumentError) { debts.first.payoff_amount(-1) }
end

test "payoff_amount throws error with nil" do
assert_raises(ArgumentError) { debts.first.payoff_amount(nil) }
end
end