Last active
May 4, 2020 19:01
-
-
Save delbetu/5bae26f7734bd46a75c97a95e4f03494 to your computer and use it in GitHub Desktop.
Spec for finance lease
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LeaseLiability | |
attr_reader :year, :fixed_payment, :annual_increase, :discount_rate, :initial_direct_cost, | |
:cash_incentives | |
attr_accessor :beginning_balance | |
def initialize( | |
year:, | |
fixed_payment:, | |
annual_increase:, | |
discount_rate:, | |
initial_direct_cost:, | |
cash_incentives: | |
) | |
@year = year | |
@fixed_payment = fixed_payment | |
@annual_increase = annual_increase | |
@discount_rate = discount_rate | |
@initial_direct_cost = initial_direct_cost | |
@cash_incentives = cash_incentives | |
end | |
def ending_balance | |
(beginning_balance + interest_expense - lease_payment).round(2) | |
end | |
def interest_expense | |
beginning_balance * discount_rate | |
end | |
def lease_payment | |
(fixed_payment * (1 + annual_increase)**(year - 1)).round(1) | |
end | |
def pv_lease_payment | |
(lease_payment / (1 + discount_rate)**year).round(1) | |
end | |
end | |
class RouAsset | |
attr_accessor :beginning_balance, :asset_amortization, :ending_balance | |
def initialize(year:) | |
@year = year | |
end | |
end | |
class FinanceLeaseCalculator | |
def initialize(years:, fixed_payment:, annual_increase:, discount_rate:, | |
initial_direct_cost:, cash_incentives:) | |
@years = years | |
@lease_liabilities = years.reduce({}) do |acum, year| | |
lease_liability = LeaseLiability.new( | |
year: year, | |
fixed_payment: fixed_payment, | |
annual_increase: annual_increase, | |
discount_rate: discount_rate, | |
initial_direct_cost: initial_direct_cost, | |
cash_incentives: cash_incentives | |
) | |
@initial_direct_cost = initial_direct_cost | |
@cash_incentives = cash_incentives | |
acum.merge(year => lease_liability) | |
end | |
calculate_ll_beginning_balances | |
@rou_assets = years.reduce({}) do |acum, year| | |
acum.merge(year => RouAsset.new(year: year)) | |
end | |
calculate_rou_asset_values | |
end | |
def lease_liability(year:) | |
@lease_liabilities[year] | |
end | |
def total_lease_payment | |
@lease_liabilities.values.map(&:lease_payment).reduce(&:+) | |
end | |
def total_pv_lease_payment | |
@lease_liabilities.values.map(&:pv_lease_payment).reduce(&:+).round(0) | |
end | |
def rou_asset(year:) | |
@rou_assets[year] | |
end | |
private | |
attr_reader :initial_direct_cost, :cash_incentives | |
def calculate_ll_beginning_balances | |
next_beginning_balance = total_pv_lease_payment | |
@lease_liabilities.each_value do |ll| | |
ll.beginning_balance = next_beginning_balance | |
next_beginning_balance = ll.ending_balance | |
end | |
end | |
def calculate_rou_asset_values | |
next_beginning_balance = total_pv_lease_payment + initial_direct_cost - cash_incentives | |
number_of_periods = @years.size | |
@rou_assets.each_value do |rou_asset| | |
rou_asset.beginning_balance = next_beginning_balance | |
rou_asset.asset_amortization = rou_asset.beginning_balance / number_of_periods | |
rou_asset.ending_balance = rou_asset.beginning_balance - rou_asset.asset_amortization | |
next_beginning_balance = rou_asset.ending_balance | |
end | |
end | |
end | |
describe FinanceLeaseCalculator do | |
it "receives" do | |
lc = FinanceLeaseCalculator.new( | |
years: 1..5, | |
fixed_payment: 15_000, | |
annual_increase: 0.025, # percentage | |
discount_rate: 0.065, # percentage | |
initial_direct_cost: 750, | |
cash_incentives: 2_500 | |
) | |
first_lease_liability = lc.lease_liability(year: 1) | |
expect(lc.total_lease_payment).to eq(78_845.0) | |
expect(lc.total_pv_lease_payment).to eq(65_328.0) | |
expect(first_lease_liability.beginning_balance).to eq(65_328.0) | |
expect(first_lease_liability.interest_expense).to eq(4_246.32) | |
expect(first_lease_liability.lease_payment).to eq(15_000) | |
expect(first_lease_liability.pv_lease_payment).to eq(14_084.5) | |
expect(first_lease_liability.ending_balance).to eq(54_574.32) | |
first_rou_asset = lc.rou_asset(year: 1) | |
expect(first_rou_asset.beginning_balance).to eq(63_578.00) | |
expect(first_rou_asset.asset_amortization).to eq(12_715.00) | |
expect(first_rou_asset.ending_balance).to eq(50_863) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LeaseLiability | |
attr_reader :year, :fixed_payment, :annual_increase, :discount_rate, :initial_direct_cost, | |
:cash_incentives | |
attr_accessor :beginning_balance | |
def initialize( | |
year:, | |
fixed_payment:, | |
annual_increase:, | |
discount_rate:, | |
initial_direct_cost:, | |
cash_incentives: | |
) | |
@year = year | |
@fixed_payment = fixed_payment | |
@annual_increase = annual_increase | |
@discount_rate = discount_rate | |
@initial_direct_cost = initial_direct_cost | |
@cash_incentives = cash_incentives | |
end | |
def ending_balance | |
(beginning_balance + liability_accretion - lease_payment).round(2) | |
end | |
def liability_accretion | |
beginning_balance * discount_rate | |
end | |
def lease_payment | |
(fixed_payment * (1 + annual_increase)**(year - 1)).round(1) | |
end | |
def pv_lease_payment | |
(lease_payment / (1 + discount_rate)**year).round(1) | |
end | |
end | |
class RouAsset | |
attr_accessor :beginning_balance, :ending_balance, :lease_cost, :asset_reduction | |
def initialize(year:) | |
@year = year | |
end | |
end | |
class OperatingLeaseCalculator | |
def initialize(years:, fixed_payment:, annual_increase:, discount_rate:, | |
initial_direct_cost:, cash_incentives:) | |
@years = years | |
@lease_liabilities = years.reduce({}) do |acum, year| | |
lease_liability = LeaseLiability.new( | |
year: year, | |
fixed_payment: fixed_payment, | |
annual_increase: annual_increase, | |
discount_rate: discount_rate, | |
initial_direct_cost: initial_direct_cost, | |
cash_incentives: cash_incentives | |
) | |
@initial_direct_cost = initial_direct_cost | |
@cash_incentives = cash_incentives | |
acum.merge(year => lease_liability) | |
end | |
calculate_ll_beginning_balances | |
@rou_assets = years.reduce({}) do |acum, year| | |
acum.merge(year => RouAsset.new(year: year)) | |
end | |
calculate_rou_asset_values | |
end | |
def lease_liability(year:) | |
@lease_liabilities[year] | |
end | |
def total_lease_payment | |
@lease_liabilities.values.map(&:lease_payment).reduce(&:+) | |
end | |
def total_pv_lease_payment | |
@lease_liabilities.values.map(&:pv_lease_payment).reduce(&:+).round(0) | |
end | |
def rou_asset(year:) | |
@rou_assets[year] | |
end | |
private | |
attr_reader :initial_direct_cost, :cash_incentives | |
def calculate_ll_beginning_balances | |
next_beginning_balance = total_pv_lease_payment | |
@lease_liabilities.each_value do |ll| | |
ll.beginning_balance = next_beginning_balance | |
next_beginning_balance = ll.ending_balance | |
end | |
end | |
def calculate_rou_asset_values | |
next_beginning_balance = total_pv_lease_payment + initial_direct_cost - cash_incentives | |
number_of_periods = @years.size | |
lease_cost = (total_lease_payment + initial_direct_cost - cash_incentives) / number_of_periods | |
@rou_assets.each do |year, rou_asset| | |
rou_asset.beginning_balance = next_beginning_balance | |
rou_asset.lease_cost = lease_cost | |
rou_asset.asset_reduction = | |
rou_asset.lease_cost - lease_liability(year: year).liability_accretion | |
rou_asset.ending_balance = rou_asset.beginning_balance - rou_asset.asset_reduction | |
next_beginning_balance = rou_asset.ending_balance | |
end | |
end | |
end | |
describe OperatingLeaseCalculator do | |
it "receives" do | |
lc = OperatingLeaseCalculator.new( | |
years: 1..5, | |
fixed_payment: 15_000, | |
annual_increase: 0.025, # percentage | |
discount_rate: 0.065, # percentage | |
initial_direct_cost: 750, | |
cash_incentives: 2_500 | |
) | |
first_lease_liability = lc.lease_liability(year: 1) | |
expect(lc.total_lease_payment).to eq(78_845.0) | |
expect(lc.total_pv_lease_payment).to eq(65_328.0) | |
expect(first_lease_liability.lease_payment).to eq(15_000) | |
expect(first_lease_liability.pv_lease_payment).to eq(14_084.5) | |
expect(first_lease_liability.beginning_balance).to eq(65_328.0) | |
expect(first_lease_liability.liability_accretion).to eq(4_246.32) | |
expect(first_lease_liability.ending_balance).to eq(54_574.32) | |
first_rou_asset = lc.rou_asset(year: 1) | |
expect(first_rou_asset.beginning_balance).to eq(63_578.00) | |
expect(first_rou_asset.lease_cost).to eq(15_419.00) | |
expect(first_rou_asset.asset_reduction).to eq(11_172.68) | |
expect(first_rou_asset.ending_balance).to eq(52_405.32) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class AccountingLeasesController < ApplicationController | |
def show | |
al = AccountingLease.find(params[:id]) | |
# This link model-calculator could be part of AcountingLease model initializer | |
lc = LeaseCalculator.new( | |
type: al.type, | |
periods: al.periods, | |
fixed_payment: al.fixed_payment, | |
annual_increase: al.annual_increase, | |
discount_rate: al.discount_rate, | |
initial_direct_cost: al.initial_direct_cost, | |
cash_incentives: al.cash_incentives | |
) | |
al.lease_liabilities = lc.lease_liabilities | |
al.rou_assets = lc.rou_assets | |
al.total_lease_payment = lc.total_lease_payment | |
al.total_pv_lease_payment = lc.total_pv_lease_payment | |
AccountingLeaseSerializer.new(accounting_lease: lc, calculator: lc) | |
end | |
end | |
class AccountingLeaseSerializer | |
include FastJsonapi::ObjectSerializer | |
attributes :id, | |
:name, | |
:location, | |
:total_lease_payment, | |
:total_pv_lease_payment | |
has_many lease_liabilities, serializer: LeaseLiabilitySerializer | |
has_many rou_assets, serializer: RouAssetSerializer | |
end | |
class LeaseLiabilitySerializer | |
include FastJsonapi::ObjectSerializer | |
attributes :beginning_balance, | |
:interest_expense, | |
:lease_payment, | |
:pv_lease_payment, | |
:ending_balance | |
end | |
class RouAssetSerializer | |
include FastJsonapi::ObjectSerializer | |
attributes :beginning_balance, | |
:asset_amortization, | |
:ending_balance | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LeaseLiability | |
attr_reader :period, :fixed_payment, :annual_increase, :discount_rate, :initial_direct_cost, | |
:cash_incentives | |
attr_accessor :beginning_balance | |
def initialize( | |
period:, | |
fixed_payment:, | |
annual_increase:, | |
discount_rate:, | |
initial_direct_cost:, | |
cash_incentives: | |
) | |
@period = period | |
@fixed_payment = fixed_payment | |
@annual_increase = annual_increase | |
@discount_rate = discount_rate | |
@initial_direct_cost = initial_direct_cost | |
@cash_incentives = cash_incentives | |
end | |
def ending_balance | |
(beginning_balance + interest_expense - lease_payment).round(2) | |
end | |
def interest_expense | |
beginning_balance * discount_rate | |
end | |
alias liability_accretion interest_expense | |
def lease_payment | |
(fixed_payment * (1 + annual_increase)**(period - 1)).round(1) | |
end | |
def pv_lease_payment | |
(lease_payment / (1 + discount_rate)**period).round(1) | |
end | |
def ending_balance | |
(beginning_balance + liability_accretion - lease_payment).round(2) | |
end | |
end | |
class LeaseCalculator | |
def initialize(type:, periods:, fixed_payment:, annual_increase:, discount_rate:, | |
initial_direct_cost:, cash_incentives:) | |
@type = type | |
@periods = periods | |
@lease_liabilities = periods.reduce({}) do |acum, period| | |
lease_liability = LeaseLiability.new( | |
period: period, | |
fixed_payment: fixed_payment, | |
annual_increase: annual_increase, | |
discount_rate: discount_rate, | |
initial_direct_cost: initial_direct_cost, | |
cash_incentives: cash_incentives | |
) | |
@initial_direct_cost = initial_direct_cost | |
@cash_incentives = cash_incentives | |
acum.merge(period => lease_liability) | |
end | |
calculate_ll_beginning_balances | |
@rou_assets = periods.reduce({}) do |acum, period| | |
acum.merge(period => RouAsset.new(period: period)) | |
end | |
if @type == 'finance' | |
calculate_finance_rou_asset_values | |
else | |
calculate_operating_rou_asset_values | |
end | |
end | |
def lease_liability(period:) | |
@lease_liabilities[period] | |
end | |
def total_lease_payment | |
@lease_liabilities.values.map(&:lease_payment).reduce(&:+) | |
end | |
def total_pv_lease_payment | |
@lease_liabilities.values.map(&:pv_lease_payment).reduce(&:+).round(0) | |
end | |
def rou_asset(period:) | |
@rou_assets[period] | |
end | |
private | |
attr_reader :initial_direct_cost, :cash_incentives | |
def calculate_ll_beginning_balances | |
next_beginning_balance = total_pv_lease_payment | |
@lease_liabilities.each_value do |ll| | |
ll.beginning_balance = next_beginning_balance | |
next_beginning_balance = ll.ending_balance | |
end | |
end | |
def calculate_finance_rou_asset_values | |
next_beginning_balance = total_pv_lease_payment + initial_direct_cost - cash_incentives | |
number_of_periods = @periods.size | |
@rou_assets.each_value do |rou_asset| | |
rou_asset.beginning_balance = next_beginning_balance | |
rou_asset.asset_amortization = rou_asset.beginning_balance / number_of_periods | |
rou_asset.ending_balance = rou_asset.beginning_balance - rou_asset.asset_amortization | |
next_beginning_balance = rou_asset.ending_balance | |
end | |
end | |
def calculate_operating_rou_asset_values | |
next_beginning_balance = total_pv_lease_payment + initial_direct_cost - cash_incentives | |
number_of_periods = @periods.size | |
lease_cost = (total_lease_payment + initial_direct_cost - cash_incentives) / number_of_periods | |
@rou_assets.each do |period, rou_asset| | |
rou_asset.beginning_balance = next_beginning_balance | |
rou_asset.lease_cost = lease_cost | |
rou_asset.asset_reduction = | |
rou_asset.lease_cost - lease_liability(period: period).liability_accretion | |
rou_asset.ending_balance = rou_asset.beginning_balance - rou_asset.asset_reduction | |
next_beginning_balance = rou_asset.ending_balance | |
end | |
end | |
end | |
class RouAsset | |
attr_accessor :beginning_balance, :ending_balance, :lease_cost, :asset_reduction, :asset_amortization | |
def initialize(period:) | |
@period = period | |
end | |
end | |
def example | |
periods = 1..5 | |
lc = LeaseCalculator.new( | |
type: 'finance', | |
periods: periods, | |
fixed_payment: 15_000, | |
annual_increase: 0.025, # percentage | |
discount_rate: 0.065, # percentage | |
initial_direct_cost: 750, | |
cash_incentives: 2_500 | |
) | |
puts 'period beginning_balance interest_expense lease_payment pv_lease_payment ending_balance beginning_balance asset_amortization ending_balance' | |
periods.each do |p| | |
ll_row = lc.lease_liability(period: p) | |
rou_row = lc.rou_asset(period: p) | |
puts "#{p} #{ll_row.beginning_balance} #{ll_row.interest_expense} #{ll_row.lease_payment} #{ll_row.pv_lease_payment} #{ll_row.ending_balance} #{rou_row.beginning_balance} #{rou_row.asset_amortization} #{rou_row.ending_balance}" | |
end | |
puts 'totals total_lease_payment total_pv_lease_payment' | |
puts "#{lc.total_lease_payment} #{lc.total_pv_lease_payment}" | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require_relative 'lease_calculator' | |
describe LeaseCalculator do | |
it "can calculate values for operating leases" do | |
lc = LeaseCalculator.new( | |
type: 'operating', | |
periods: 1..5, | |
fixed_payment: 15_000, | |
annual_increase: 0.025, # percentage | |
discount_rate: 0.065, # percentage | |
initial_direct_cost: 750, | |
cash_incentives: 2_500 | |
) | |
first_lease_liability = lc.lease_liability(period: 1) | |
expect(lc.total_lease_payment).to eq(78_845.0) | |
expect(lc.total_pv_lease_payment).to eq(65_328.0) | |
expect(first_lease_liability.lease_payment).to eq(15_000) | |
expect(first_lease_liability.pv_lease_payment).to eq(14_084.5) | |
expect(first_lease_liability.beginning_balance).to eq(65_328.0) | |
expect(first_lease_liability.liability_accretion).to eq(4_246.32) | |
expect(first_lease_liability.ending_balance).to eq(54_574.32) | |
first_rou_asset = lc.rou_asset(period: 1) | |
expect(first_rou_asset.beginning_balance).to eq(63_578.00) | |
expect(first_rou_asset.lease_cost).to eq(15_419.00) | |
expect(first_rou_asset.asset_reduction).to eq(11_172.68) | |
expect(first_rou_asset.ending_balance).to eq(52_405.32) | |
end | |
it "can calculate values for finance leases" do | |
lc = LeaseCalculator.new( | |
type: 'finance', | |
periods: 1..5, | |
fixed_payment: 15_000, | |
annual_increase: 0.025, # percentage | |
discount_rate: 0.065, # percentage | |
initial_direct_cost: 750, | |
cash_incentives: 2_500 | |
) | |
first_lease_liability = lc.lease_liability(period: 1) | |
expect(lc.total_lease_payment).to eq(78_845.0) | |
expect(lc.total_pv_lease_payment).to eq(65_328.0) | |
expect(first_lease_liability.beginning_balance).to eq(65_328.0) | |
expect(first_lease_liability.interest_expense).to eq(4_246.32) | |
expect(first_lease_liability.lease_payment).to eq(15_000) | |
expect(first_lease_liability.pv_lease_payment).to eq(14_084.5) | |
expect(first_lease_liability.ending_balance).to eq(54_574.32) | |
first_rou_asset = lc.rou_asset(period: 1) | |
expect(first_rou_asset.beginning_balance).to eq(63_578.00) | |
expect(first_rou_asset.asset_amortization).to eq(12_715.00) | |
expect(first_rou_asset.ending_balance).to eq(50_863) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment