Skip to content

Instantly share code, notes, and snippets.

@delbetu
Last active May 4, 2020 19:01
Show Gist options
  • Save delbetu/5bae26f7734bd46a75c97a95e4f03494 to your computer and use it in GitHub Desktop.
Save delbetu/5bae26f7734bd46a75c97a95e4f03494 to your computer and use it in GitHub Desktop.
Spec for finance lease
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
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
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
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
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