mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 21:29:38 +02:00
Add loan and credit card views (#1268)
* Add loan and credit card views * Lint fix * Clean up overview card markup * Lint fix * Test fix
This commit is contained in:
parent
9263dd3bbe
commit
fd941d714d
34 changed files with 564 additions and 102 deletions
|
@ -41,14 +41,11 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
|
@account.accountable.build_address if @account.accountable.is_a?(Property) && @account.accountable.address.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
Account.transaction do
|
@account.update_with_sync!(account_params)
|
||||||
@account.update! account_params.except(:accountable_type, :balance)
|
|
||||||
@account.update_balance!(account_params[:balance]) if account_params[:balance]
|
|
||||||
end
|
|
||||||
@account.sync_later
|
|
||||||
redirect_back_or_to account_path(@account), notice: t(".success")
|
redirect_back_or_to account_path(@account), notice: t(".success")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
41
app/controllers/credit_cards_controller.rb
Normal file
41
app/controllers/credit_cards_controller.rb
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
class CreditCardsController < ApplicationController
|
||||||
|
before_action :set_account, only: :update
|
||||||
|
|
||||||
|
def create
|
||||||
|
account = Current.family
|
||||||
|
.accounts
|
||||||
|
.create_with_optional_start_balance! \
|
||||||
|
attributes: account_params.except(:start_date, :start_balance),
|
||||||
|
start_date: account_params[:start_date],
|
||||||
|
start_balance: account_params[:start_balance]
|
||||||
|
|
||||||
|
account.sync_later
|
||||||
|
redirect_to account, notice: t(".success")
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@account.update_with_sync!(account_params)
|
||||||
|
redirect_to @account, notice: t(".success")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Current.family.accounts.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_params
|
||||||
|
params.require(:account)
|
||||||
|
.permit(
|
||||||
|
:name, :balance, :start_date, :start_balance, :currency, :accountable_type,
|
||||||
|
accountable_attributes: [
|
||||||
|
:id,
|
||||||
|
:available_credit,
|
||||||
|
:minimum_payment,
|
||||||
|
:apr,
|
||||||
|
:annual_fee,
|
||||||
|
:expiration_date
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
39
app/controllers/loans_controller.rb
Normal file
39
app/controllers/loans_controller.rb
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
class LoansController < ApplicationController
|
||||||
|
before_action :set_account, only: :update
|
||||||
|
|
||||||
|
def create
|
||||||
|
account = Current.family
|
||||||
|
.accounts
|
||||||
|
.create_with_optional_start_balance! \
|
||||||
|
attributes: account_params.except(:start_date, :start_balance),
|
||||||
|
start_date: account_params[:start_date],
|
||||||
|
start_balance: account_params[:start_balance]
|
||||||
|
|
||||||
|
account.sync_later
|
||||||
|
redirect_to account, notice: t(".success")
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@account.update_with_sync!(account_params)
|
||||||
|
redirect_to @account, notice: t(".success")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Current.family.accounts.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_params
|
||||||
|
params.require(:account)
|
||||||
|
.permit(
|
||||||
|
:name, :balance, :start_date, :start_balance, :currency, :accountable_type,
|
||||||
|
accountable_attributes: [
|
||||||
|
:id,
|
||||||
|
:rate_type,
|
||||||
|
:interest_rate,
|
||||||
|
:term_months
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,8 +14,7 @@ class PropertiesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@account.update!(account_params)
|
@account.update_with_sync!(account_params)
|
||||||
@account.sync_later
|
|
||||||
redirect_to @account, notice: t(".success")
|
redirect_to @account, notice: t(".success")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,7 @@ class VehiclesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@account.update!(account_params)
|
@account.update_with_sync!(account_params)
|
||||||
@account.sync_later
|
|
||||||
redirect_to @account, notice: t(".success")
|
redirect_to @account, notice: t(".success")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
module AccountsHelper
|
module AccountsHelper
|
||||||
|
def summary_card(title:, &block)
|
||||||
|
content = capture(&block)
|
||||||
|
render "accounts/summary_card", title: title, content: content
|
||||||
|
end
|
||||||
|
|
||||||
def to_accountable_title(accountable)
|
def to_accountable_title(accountable)
|
||||||
accountable.model_name.human
|
accountable.model_name.human
|
||||||
end
|
end
|
||||||
|
@ -31,6 +36,10 @@ module AccountsHelper
|
||||||
properties_path
|
properties_path
|
||||||
when "Vehicle"
|
when "Vehicle"
|
||||||
vehicles_path
|
vehicles_path
|
||||||
|
when "Loan"
|
||||||
|
loans_path
|
||||||
|
when "CreditCard"
|
||||||
|
credit_cards_path
|
||||||
else
|
else
|
||||||
accounts_path
|
accounts_path
|
||||||
end
|
end
|
||||||
|
@ -42,6 +51,10 @@ module AccountsHelper
|
||||||
property_path(account)
|
property_path(account)
|
||||||
when "Vehicle"
|
when "Vehicle"
|
||||||
vehicle_path(account)
|
vehicle_path(account)
|
||||||
|
when "Loan"
|
||||||
|
loan_path(account)
|
||||||
|
when "CreditCard"
|
||||||
|
credit_card_path(account)
|
||||||
else
|
else
|
||||||
account_path(account)
|
account_path(account)
|
||||||
end
|
end
|
||||||
|
@ -58,6 +71,7 @@ module AccountsHelper
|
||||||
return [ value_tab ] if account.other_asset? || account.other_liability?
|
return [ value_tab ] if account.other_asset? || account.other_liability?
|
||||||
return [ overview_tab, value_tab ] if account.property? || account.vehicle?
|
return [ overview_tab, value_tab ] if account.property? || account.vehicle?
|
||||||
return [ holdings_tab, cash_tab, trades_tab ] if account.investment?
|
return [ holdings_tab, cash_tab, trades_tab ] if account.investment?
|
||||||
|
return [ overview_tab, value_tab, transactions_tab ] if account.loan? || account.credit_card?
|
||||||
|
|
||||||
[ value_tab, transactions_tab ]
|
[ value_tab, transactions_tab ]
|
||||||
end
|
end
|
||||||
|
|
|
@ -137,12 +137,16 @@ module ApplicationHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_money(number_or_money, options = {})
|
def format_money(number_or_money, options = {})
|
||||||
|
return nil unless number_or_money
|
||||||
|
|
||||||
money = Money.new(number_or_money)
|
money = Money.new(number_or_money)
|
||||||
options.reverse_merge!(money.format_options(I18n.locale))
|
options.reverse_merge!(money.format_options(I18n.locale))
|
||||||
number_to_currency(money.amount, options)
|
number_to_currency(money.amount, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_money_without_symbol(number_or_money, options = {})
|
def format_money_without_symbol(number_or_money, options = {})
|
||||||
|
return nil unless number_or_money
|
||||||
|
|
||||||
money = Money.new(number_or_money)
|
money = Money.new(number_or_money)
|
||||||
options.reverse_merge!(money.format_options(I18n.locale))
|
options.reverse_merge!(money.format_options(I18n.locale))
|
||||||
ActiveSupport::NumberHelper.number_to_delimited(money.amount.round(options[:precision] || 0), { delimiter: options[:delimiter], separator: options[:separator] })
|
ActiveSupport::NumberHelper.number_to_delimited(money.amount.round(options[:precision] || 0), { delimiter: options[:delimiter], separator: options[:separator] })
|
||||||
|
|
|
@ -82,6 +82,10 @@ class Account < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def original_balance
|
||||||
|
balances.chronological.first&.balance || balance
|
||||||
|
end
|
||||||
|
|
||||||
def owns_ticker?(ticker)
|
def owns_ticker?(ticker)
|
||||||
security_id = Security.find_by(ticker: ticker)&.id
|
security_id = Security.find_by(ticker: ticker)&.id
|
||||||
entries.account_trades
|
entries.account_trades
|
||||||
|
@ -93,6 +97,15 @@ class Account < ApplicationRecord
|
||||||
classification == "asset" ? "up" : "down"
|
classification == "asset" ? "up" : "down"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_with_sync!(attributes)
|
||||||
|
transaction do
|
||||||
|
update!(attributes)
|
||||||
|
update_balance!(attributes[:balance]) if attributes[:balance]
|
||||||
|
end
|
||||||
|
|
||||||
|
sync_later
|
||||||
|
end
|
||||||
|
|
||||||
def update_balance!(balance)
|
def update_balance!(balance)
|
||||||
valuation = entries.account_valuations.find_by(date: Date.current)
|
valuation = entries.account_valuations.find_by(date: Date.current)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
class Address < ApplicationRecord
|
class Address < ApplicationRecord
|
||||||
belongs_to :addressable, polymorphic: true
|
belongs_to :addressable, polymorphic: true
|
||||||
|
|
||||||
validates :line1, :locality, presence: true
|
|
||||||
validates :postal_code, presence: true, if: :postal_code_required?
|
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
I18n.t("address.format",
|
I18n.t("address.format",
|
||||||
line1: line1,
|
line1: line1,
|
||||||
|
@ -15,10 +12,4 @@ class Address < ApplicationRecord
|
||||||
postal_code: postal_code
|
postal_code: postal_code
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def postal_code_required?
|
|
||||||
country.in?(%w[US CA GB])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,7 @@ module Accountable
|
||||||
if balance_series.empty? && period.date_range.end == Date.current
|
if balance_series.empty? && period.date_range.end == Date.current
|
||||||
TimeSeries.new([ { date: Date.current, value: account.balance_money.exchange_to(currency) } ])
|
TimeSeries.new([ { date: Date.current, value: account.balance_money.exchange_to(currency) } ])
|
||||||
else
|
else
|
||||||
TimeSeries.from_collection(balance_series, :balance_money)
|
TimeSeries.from_collection(balance_series, :balance_money, favorable_direction: account.asset? ? "up" : "down")
|
||||||
end
|
end
|
||||||
rescue Money::ConversionError
|
rescue Money::ConversionError
|
||||||
TimeSeries.new([])
|
TimeSeries.new([])
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
class Loan < ApplicationRecord
|
class Loan < ApplicationRecord
|
||||||
include Accountable
|
include Accountable
|
||||||
|
|
||||||
|
def monthly_payment
|
||||||
|
return nil if term_months.nil? || interest_rate.nil? || rate_type.nil? || rate_type != "fixed"
|
||||||
|
return Money.new(0, account.currency) if account.original_balance.zero? || term_months.zero?
|
||||||
|
|
||||||
|
annual_rate = interest_rate / 100.0
|
||||||
|
monthly_rate = annual_rate / 12.0
|
||||||
|
|
||||||
|
payment = (account.original_balance * monthly_rate * (1 + monthly_rate)**term_months) / ((1 + monthly_rate)**term_months - 1)
|
||||||
|
|
||||||
|
Money.new(payment.round, account.currency)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,14 +3,14 @@ class TimeSeries
|
||||||
|
|
||||||
attr_reader :values, :favorable_direction
|
attr_reader :values, :favorable_direction
|
||||||
|
|
||||||
def self.from_collection(collection, value_method)
|
def self.from_collection(collection, value_method, favorable_direction: "up")
|
||||||
collection.map do |obj|
|
collection.map do |obj|
|
||||||
{
|
{
|
||||||
date: obj.date,
|
date: obj.date,
|
||||||
value: obj.public_send(value_method),
|
value: obj.public_send(value_method),
|
||||||
original: obj
|
original: obj
|
||||||
}
|
}
|
||||||
end.then { |data| new(data) }
|
end.then { |data| new(data, favorable_direction: favorable_direction) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(data, favorable_direction: "up")
|
def initialize(data, favorable_direction: "up")
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<%# locals: (account:) %>
|
<%# locals: (account:) %>
|
||||||
|
|
||||||
<%= render partial: "accounts/accountables/#{account.accountable_type.downcase}/overview", locals: { account: account } %>
|
<%= render partial: "accounts/accountables/#{account.accountable_type.underscore}/overview", locals: { account: account } %>
|
||||||
|
|
8
app/views/accounts/_summary_card.html.erb
Normal file
8
app/views/accounts/_summary_card.html.erb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<%# locals: (title:, content:) %>
|
||||||
|
|
||||||
|
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
||||||
|
<h4 class="text-gray-500 text-sm"><%= title %></h4>
|
||||||
|
<p class="text-xl font-medium text-gray-900">
|
||||||
|
<%= content %>
|
||||||
|
</p>
|
||||||
|
</div>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div>
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<%= f.fields_for :accountable do |credit_card_form| %>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<%= credit_card_form.text_field :available_credit, label: t(".available_credit"), placeholder: t(".available_credit_placeholder") %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<%= credit_card_form.text_field :minimum_payment, label: t(".minimum_payment"), placeholder: t(".minimum_payment_placeholder") %>
|
||||||
|
<%= credit_card_form.text_field :apr, label: t(".apr"), placeholder: t(".apr_placeholder") %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<%= credit_card_form.date_field :expiration_date, label: t(".expiration_date") %>
|
||||||
|
<%= credit_card_form.text_field :annual_fee, label: t(".annual_fee"), placeholder: t(".annual_fee_placeholder") %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<div>
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<%= f.fields_for :accountable do |loan_form| %>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<%= loan_form.text_field :interest_rate, label: t(".interest_rate"), placeholder: t(".interest_rate_placeholder") %>
|
||||||
|
<%= loan_form.select :rate_type, options_for_select([["Fixed", "fixed"], ["Variable", "variable"], ["Adjustable", "adjustable"]]), { label: t(".rate_type") } %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<%= loan_form.number_field :term_months, label: t(".term_months"), placeholder: t(".term_months_placeholder") %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -3,6 +3,8 @@
|
||||||
<div>
|
<div>
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<h3 class="my-4 font-medium"><%= t(".additional_info") %> (<%= t(".optional") %>)</h3>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= f.fields_for :accountable do |af| %>
|
<%= f.fields_for :accountable do |af| %>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
@ -15,18 +17,18 @@
|
||||||
|
|
||||||
<%= af.fields_for :address do |address_form| %>
|
<%= af.fields_for :address do |address_form| %>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<%= address_form.text_field :line1, label: t(".line1"), placeholder: "123 Main St", required: true %>
|
<%= address_form.text_field :line1, label: t(".line1"), placeholder: "123 Main St" %>
|
||||||
<%= address_form.text_field :line2, label: t(".line2"), placeholder: "Apt 1" %>
|
<%= address_form.text_field :line2, label: t(".line2"), placeholder: "Apt 1" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<%= address_form.text_field :locality, label: t(".city"), placeholder: "Sacramento", required: true %>
|
<%= address_form.text_field :locality, label: t(".city"), placeholder: "Sacramento" %>
|
||||||
<%= address_form.text_field :region, label: t(".state"), placeholder: "CA", required: true %>
|
<%= address_form.text_field :region, label: t(".state"), placeholder: "CA" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<%= address_form.text_field :postal_code, label: t(".postal_code"), placeholder: "95814" %>
|
<%= address_form.text_field :postal_code, label: t(".postal_code"), placeholder: "95814" %>
|
||||||
<%= address_form.text_field :country, label: t(".country"), placeholder: "USA", required: true %>
|
<%= address_form.text_field :country, label: t(".country"), placeholder: "USA" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<%# locals: (account:) %>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-3 gap-2">
|
||||||
|
<%= summary_card title: t(".amount_owed") do %>
|
||||||
|
<%= format_money(account.balance) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= summary_card title: t(".available_credit") do %>
|
||||||
|
<%= format_money(account.credit_card.available_credit) || t(".unknown") %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= summary_card title: t(".minimum_payment") do %>
|
||||||
|
<%= format_money(account.credit_card.minimum_payment) || t(".unknown") %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= summary_card title: t(".apr") do %>
|
||||||
|
<%= account.credit_card.apr ? number_to_percentage(account.credit_card.apr, precision: 2) : t(".unknown") %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= summary_card title: t(".expiration_date") do %>
|
||||||
|
<%= account.credit_card.expiration_date ? l(account.credit_card.expiration_date, format: :long) : t(".unknown") %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= summary_card title: t(".annual_fee") do %>
|
||||||
|
<%= format_money(account.credit_card.annual_fee) || t(".unknown") %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
41
app/views/accounts/accountables/loan/_overview.html.erb
Normal file
41
app/views/accounts/accountables/loan/_overview.html.erb
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<%# locals: (account:) %>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-3 gap-2">
|
||||||
|
<%= summary_card title: t(".original_principal") do %>
|
||||||
|
<%= format_money account.original_balance %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= summary_card title: t(".remaining_principal") do %>
|
||||||
|
<%= format_money account.balance %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= summary_card title: t(".interest_rate") do %>
|
||||||
|
<% if account.loan.interest_rate.present? %>
|
||||||
|
<%= number_to_percentage(account.loan.interest_rate, precision: 2) %>
|
||||||
|
<% else %>
|
||||||
|
<%= t(".unknown") %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= summary_card title: t(".monthly_payment") do %>
|
||||||
|
<% if account.loan.rate_type.present? && account.loan.rate_type != 'fixed' %>
|
||||||
|
<%= t(".not_applicable") %>
|
||||||
|
<% elsif account.loan.rate_type == 'fixed' && account.loan.monthly_payment.present? %>
|
||||||
|
<%= format_money(account.loan.monthly_payment) %>
|
||||||
|
<% else %>
|
||||||
|
<%= t(".unknown") %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= summary_card title: t(".term") do %>
|
||||||
|
<% if account.loan.term_months.present? %>
|
||||||
|
<%= pluralize(account.loan.term_months / 12, "year") %>
|
||||||
|
<% else %>
|
||||||
|
<%= t(".unknown") %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= summary_card title: t(".type") do %>
|
||||||
|
<%= account.loan.rate_type&.titleize || t(".unknown") %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
|
@ -1,20 +1,15 @@
|
||||||
<%# locals: (account:) %>
|
<%# locals: (account:) %>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 gap-2">
|
<div class="grid grid-cols-3 gap-2">
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".market_value") do %>
|
||||||
<h4 class="text-gray-500 text-sm"><%= t(".market_value") %></h4>
|
<%= format_money(account.balance_money) %>
|
||||||
<p class="text-xl font-medium text-gray-900"><%= format_money(account.balance_money) %></p>
|
<% end %>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".purchase_price") do %>
|
||||||
<h4 class="text-gray-500 text-sm"><%= t(".purchase_price") %></h4>
|
|
||||||
<p class="text-xl font-medium text-gray-900">
|
|
||||||
<%= account.property.purchase_price ? format_money(account.property.purchase_price) : t(".unknown") %>
|
<%= account.property.purchase_price ? format_money(account.property.purchase_price) : t(".unknown") %>
|
||||||
</p>
|
<% end %>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".trend") do %>
|
||||||
<h4 class="text-gray-500 text-sm flex items-center gap-1"><%= t(".trend") %></h4>
|
|
||||||
<div class="flex items-center gap-1" style="color: <%= account.property.trend.color %>">
|
<div class="flex items-center gap-1" style="color: <%= account.property.trend.color %>">
|
||||||
<p class="text-xl font-medium">
|
<p class="text-xl font-medium">
|
||||||
<%= account.property.trend.value %>
|
<%= account.property.trend.value %>
|
||||||
|
@ -22,19 +17,13 @@
|
||||||
|
|
||||||
<p>(<%= account.property.trend.percent %>%)</p>
|
<p>(<%= account.property.trend.percent %>%)</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<% end %>
|
||||||
|
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".year_built") do %>
|
||||||
<h4 class="text-gray-500 text-sm"><%= t(".year_built") %></h4>
|
|
||||||
<p class="text-xl font-medium text-gray-900">
|
|
||||||
<%= account.property.year_built || t(".unknown") %>
|
<%= account.property.year_built || t(".unknown") %>
|
||||||
</p>
|
<% end %>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".living_area") do %>
|
||||||
<h4 class="text-gray-500 text-sm"><%= t(".living_area") %></h4>
|
|
||||||
<p class="text-xl font-medium text-gray-900">
|
|
||||||
<%= account.property.area || t(".unknown") %>
|
<%= account.property.area || t(".unknown") %>
|
||||||
</p>
|
<% end %>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,43 +1,27 @@
|
||||||
<%# locals: (account:) %>
|
<%# locals: (account:) %>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 gap-2">
|
<div class="grid grid-cols-3 gap-2">
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".make_model") do %>
|
||||||
<h4 class="text-gray-500 text-sm"><%= t(".make_model") %></h4>
|
|
||||||
<p class="text-xl font-medium text-gray-900">
|
|
||||||
<%= [account.vehicle.make, account.vehicle.model].compact.join(" ").presence || t(".unknown") %>
|
<%= [account.vehicle.make, account.vehicle.model].compact.join(" ").presence || t(".unknown") %>
|
||||||
</p>
|
<% end %>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".year") do %>
|
||||||
<h4 class="text-gray-500 text-sm"><%= t(".year") %></h4>
|
|
||||||
<p class="text-xl font-medium text-gray-900">
|
|
||||||
<%= account.vehicle.year || t(".unknown") %>
|
<%= account.vehicle.year || t(".unknown") %>
|
||||||
</p>
|
<% end %>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".mileage") do %>
|
||||||
<h4 class="text-gray-500 text-sm flex items-center gap-1"><%= t(".mileage") %></h4>
|
|
||||||
<p class="text-xl font-medium text-gray-900">
|
|
||||||
<%= account.vehicle.mileage || t(".unknown") %>
|
<%= account.vehicle.mileage || t(".unknown") %>
|
||||||
</p>
|
<% end %>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".purchase_price") do %>
|
||||||
<h4 class="text-gray-500 text-sm"><%= t(".purchase_price") %></h4>
|
|
||||||
<p class="text-xl font-medium text-gray-900">
|
|
||||||
<%= format_money account.vehicle.purchase_price %>
|
<%= format_money account.vehicle.purchase_price %>
|
||||||
</p>
|
<% end %>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".current_price") do %>
|
||||||
<h4 class="text-gray-500 text-sm"><%= t(".current_price") %></h4>
|
|
||||||
<p class="text-xl font-medium text-gray-900">
|
|
||||||
<%= format_money account.balance_money %>
|
<%= format_money account.balance_money %>
|
||||||
</p>
|
<% end %>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
<%= summary_card title: t(".trend") do %>
|
||||||
<h4 class="text-gray-500 text-sm"><%= t(".trend") %></h4>
|
|
||||||
<div class="flex items-center gap-1" style="color: <%= account.vehicle.trend.color %>">
|
<div class="flex items-center gap-1" style="color: <%= account.vehicle.trend.color %>">
|
||||||
<p class="text-xl font-medium">
|
<p class="text-xl font-medium">
|
||||||
<%= account.vehicle.trend.value %>
|
<%= account.vehicle.trend.value %>
|
||||||
|
@ -45,5 +29,5 @@
|
||||||
|
|
||||||
<p>(<%= account.vehicle.trend.percent %>%)</p>
|
<p>(<%= account.vehicle.trend.percent %>%)</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -60,7 +60,11 @@
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<div>
|
<div>
|
||||||
|
<% if @account.asset? %>
|
||||||
<%= tag.p t(".total_value"), class: "text-sm font-medium text-gray-500" %>
|
<%= tag.p t(".total_value"), class: "text-sm font-medium text-gray-500" %>
|
||||||
|
<% else %>
|
||||||
|
<%= tag.p t(".total_owed"), class: "text-sm font-medium text-gray-500" %>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= render "tooltip", account: @account if @account.investment? %>
|
<%= render "tooltip", account: @account if @account.investment? %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,13 +5,48 @@ en:
|
||||||
has_issues: Issue detected.
|
has_issues: Issue detected.
|
||||||
troubleshoot: Troubleshoot
|
troubleshoot: Troubleshoot
|
||||||
accountables:
|
accountables:
|
||||||
|
credit_card:
|
||||||
|
annual_fee: Annual fee
|
||||||
|
annual_fee_placeholder: '99'
|
||||||
|
apr: APR
|
||||||
|
apr_placeholder: '15.99'
|
||||||
|
available_credit: Available credit
|
||||||
|
available_credit_placeholder: '10000'
|
||||||
|
expiration_date: Expiration date
|
||||||
|
minimum_payment: Minimum payment
|
||||||
|
minimum_payment_placeholder: '100'
|
||||||
|
overview:
|
||||||
|
amount_owed: Amount Owed
|
||||||
|
annual_fee: Annual Fee
|
||||||
|
apr: APR
|
||||||
|
available_credit: Available Credit
|
||||||
|
expiration_date: Expiration Date
|
||||||
|
minimum_payment: Minimum Payment
|
||||||
|
unknown: Unknown
|
||||||
|
loan:
|
||||||
|
interest_rate: Interest rate
|
||||||
|
interest_rate_placeholder: '5.25'
|
||||||
|
overview:
|
||||||
|
interest_rate: Interest Rate
|
||||||
|
monthly_payment: Monthly Payment
|
||||||
|
not_applicable: N/A
|
||||||
|
original_principal: Original Principal
|
||||||
|
remaining_principal: Remaining Principal
|
||||||
|
term: Term
|
||||||
|
type: Type
|
||||||
|
unknown: Unknown
|
||||||
|
rate_type: Rate type
|
||||||
|
term_months: Term (months)
|
||||||
|
term_months_placeholder: '360'
|
||||||
property:
|
property:
|
||||||
|
additional_info: Additional info
|
||||||
area_unit: Area unit
|
area_unit: Area unit
|
||||||
area_value: Area value (optional)
|
area_value: Area value
|
||||||
city: City
|
city: City
|
||||||
country: Country
|
country: Country
|
||||||
line1: Address line 1
|
line1: Address line 1
|
||||||
line2: Address line 2 (optional)
|
line2: Address line 2
|
||||||
|
optional: optional
|
||||||
overview:
|
overview:
|
||||||
living_area: Living Area
|
living_area: Living Area
|
||||||
market_value: Market Value
|
market_value: Market Value
|
||||||
|
@ -19,9 +54,9 @@ en:
|
||||||
trend: Trend
|
trend: Trend
|
||||||
unknown: Unknown
|
unknown: Unknown
|
||||||
year_built: Year Built
|
year_built: Year Built
|
||||||
postal_code: Postal code (optional)
|
postal_code: Postal code
|
||||||
state: State
|
state: State
|
||||||
year_built: Year built (optional)
|
year_built: Year built
|
||||||
vehicle:
|
vehicle:
|
||||||
make: Make
|
make: Make
|
||||||
make_placeholder: Toyota
|
make_placeholder: Toyota
|
||||||
|
@ -102,6 +137,7 @@ en:
|
||||||
sync_message_missing_rates: Since exchange rates haven't been synced, balance
|
sync_message_missing_rates: Since exchange rates haven't been synced, balance
|
||||||
graphs may not reflect accurate values.
|
graphs may not reflect accurate values.
|
||||||
sync_message_unknown_error: An error has occurred during the sync.
|
sync_message_unknown_error: An error has occurred during the sync.
|
||||||
|
total_owed: Total Owed
|
||||||
total_value: Total Value
|
total_value: Total Value
|
||||||
trades: Transactions
|
trades: Transactions
|
||||||
transactions: Transactions
|
transactions: Transactions
|
||||||
|
@ -124,3 +160,13 @@ en:
|
||||||
value, minus margin loans.
|
value, minus margin loans.
|
||||||
update:
|
update:
|
||||||
success: Account updated
|
success: Account updated
|
||||||
|
credit_cards:
|
||||||
|
create:
|
||||||
|
success: Credit card created successfully
|
||||||
|
update:
|
||||||
|
success: Credit card updated successfully
|
||||||
|
loans:
|
||||||
|
create:
|
||||||
|
success: Loan created successfully
|
||||||
|
update:
|
||||||
|
success: Loan updated successfully
|
||||||
|
|
|
@ -75,6 +75,8 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
resources :properties, only: %i[create update]
|
resources :properties, only: %i[create update]
|
||||||
resources :vehicles, only: %i[create update]
|
resources :vehicles, only: %i[create update]
|
||||||
|
resources :credit_cards, only: %i[create update]
|
||||||
|
resources :loans, only: %i[create update]
|
||||||
|
|
||||||
resources :transactions, only: %i[index new create] do
|
resources :transactions, only: %i[index new create] do
|
||||||
collection do
|
collection do
|
||||||
|
|
17
db/migrate/20241008122449_add_debt_account_views.rb
Normal file
17
db/migrate/20241008122449_add_debt_account_views.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
class AddDebtAccountViews < ActiveRecord::Migration[7.2]
|
||||||
|
def change
|
||||||
|
change_table :loans do |t|
|
||||||
|
t.string :rate_type
|
||||||
|
t.decimal :interest_rate, precision: 10, scale: 2
|
||||||
|
t.integer :term_months
|
||||||
|
end
|
||||||
|
|
||||||
|
change_table :credit_cards do |t|
|
||||||
|
t.decimal :available_credit, precision: 10, scale: 2
|
||||||
|
t.decimal :minimum_payment, precision: 10, scale: 2
|
||||||
|
t.decimal :apr, precision: 10, scale: 2
|
||||||
|
t.date :expiration_date
|
||||||
|
t.decimal :annual_fee, precision: 10, scale: 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
10
db/schema.rb
generated
10
db/schema.rb
generated
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.2].define(version: 2024_10_07_211438) do
|
ActiveRecord::Schema[7.2].define(version: 2024_10_08_122449) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pgcrypto"
|
enable_extension "pgcrypto"
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -184,6 +184,11 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_07_211438) do
|
||||||
create_table "credit_cards", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
create_table "credit_cards", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.decimal "available_credit", precision: 10, scale: 2
|
||||||
|
t.decimal "minimum_payment", precision: 10, scale: 2
|
||||||
|
t.decimal "apr", precision: 10, scale: 2
|
||||||
|
t.date "expiration_date"
|
||||||
|
t.decimal "annual_fee", precision: 10, scale: 2
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "cryptos", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
create_table "cryptos", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
|
@ -408,6 +413,9 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_07_211438) do
|
||||||
create_table "loans", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
create_table "loans", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.string "rate_type"
|
||||||
|
t.decimal "interest_rate", precision: 10, scale: 2
|
||||||
|
t.integer "term_months"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "merchants", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
create_table "merchants", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
|
|
83
test/controllers/credit_cards_controller_test.rb
Normal file
83
test/controllers/credit_cards_controller_test.rb
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class CreditCardsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
setup do
|
||||||
|
sign_in @user = users(:family_admin)
|
||||||
|
@account = accounts(:credit_card)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "creates credit card" do
|
||||||
|
assert_difference -> { Account.count } => 1,
|
||||||
|
-> { CreditCard.count } => 1,
|
||||||
|
-> { Account::Valuation.count } => 2,
|
||||||
|
-> { Account::Entry.count } => 2 do
|
||||||
|
post credit_cards_path, params: {
|
||||||
|
account: {
|
||||||
|
name: "New Credit Card",
|
||||||
|
balance: 1000,
|
||||||
|
currency: "USD",
|
||||||
|
accountable_type: "CreditCard",
|
||||||
|
start_date: 1.month.ago.to_date,
|
||||||
|
start_balance: 0,
|
||||||
|
accountable_attributes: {
|
||||||
|
available_credit: 5000,
|
||||||
|
minimum_payment: 25,
|
||||||
|
apr: 15.99,
|
||||||
|
expiration_date: 2.years.from_now.to_date,
|
||||||
|
annual_fee: 99
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
created_account = Account.order(:created_at).last
|
||||||
|
|
||||||
|
assert_equal "New Credit Card", created_account.name
|
||||||
|
assert_equal 1000, created_account.balance
|
||||||
|
assert_equal "USD", created_account.currency
|
||||||
|
assert_equal 5000, created_account.credit_card.available_credit
|
||||||
|
assert_equal 25, created_account.credit_card.minimum_payment
|
||||||
|
assert_equal 15.99, created_account.credit_card.apr
|
||||||
|
assert_equal 2.years.from_now.to_date, created_account.credit_card.expiration_date
|
||||||
|
assert_equal 99, created_account.credit_card.annual_fee
|
||||||
|
|
||||||
|
assert_redirected_to account_path(created_account)
|
||||||
|
assert_equal "Credit card created successfully", flash[:notice]
|
||||||
|
assert_enqueued_with(job: AccountSyncJob)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates credit card" do
|
||||||
|
assert_no_difference [ "Account.count", "CreditCard.count" ] do
|
||||||
|
patch credit_card_path(@account), params: {
|
||||||
|
account: {
|
||||||
|
name: "Updated Credit Card",
|
||||||
|
balance: 2000,
|
||||||
|
currency: "USD",
|
||||||
|
accountable_type: "CreditCard",
|
||||||
|
accountable_attributes: {
|
||||||
|
id: @account.accountable_id,
|
||||||
|
available_credit: 6000,
|
||||||
|
minimum_payment: 50,
|
||||||
|
apr: 14.99,
|
||||||
|
expiration_date: 3.years.from_now.to_date,
|
||||||
|
annual_fee: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
@account.reload
|
||||||
|
|
||||||
|
assert_equal "Updated Credit Card", @account.name
|
||||||
|
assert_equal 2000, @account.balance
|
||||||
|
assert_equal 6000, @account.credit_card.available_credit
|
||||||
|
assert_equal 50, @account.credit_card.minimum_payment
|
||||||
|
assert_equal 14.99, @account.credit_card.apr
|
||||||
|
assert_equal 3.years.from_now.to_date, @account.credit_card.expiration_date
|
||||||
|
assert_equal 0, @account.credit_card.annual_fee
|
||||||
|
|
||||||
|
assert_redirected_to account_path(@account)
|
||||||
|
assert_equal "Credit card updated successfully", flash[:notice]
|
||||||
|
assert_enqueued_with(job: AccountSyncJob)
|
||||||
|
end
|
||||||
|
end
|
75
test/controllers/loans_controller_test.rb
Normal file
75
test/controllers/loans_controller_test.rb
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class LoansControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
setup do
|
||||||
|
sign_in @user = users(:family_admin)
|
||||||
|
@account = accounts(:loan)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "creates loan" do
|
||||||
|
assert_difference -> { Account.count } => 1,
|
||||||
|
-> { Loan.count } => 1,
|
||||||
|
-> { Account::Valuation.count } => 2,
|
||||||
|
-> { Account::Entry.count } => 2 do
|
||||||
|
post loans_path, params: {
|
||||||
|
account: {
|
||||||
|
name: "New Loan",
|
||||||
|
balance: 50000,
|
||||||
|
currency: "USD",
|
||||||
|
accountable_type: "Loan",
|
||||||
|
start_date: 1.month.ago.to_date,
|
||||||
|
start_balance: 50000,
|
||||||
|
accountable_attributes: {
|
||||||
|
interest_rate: 5.5,
|
||||||
|
term_months: 60,
|
||||||
|
rate_type: "fixed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
created_account = Account.order(:created_at).last
|
||||||
|
|
||||||
|
assert_equal "New Loan", created_account.name
|
||||||
|
assert_equal 50000, created_account.balance
|
||||||
|
assert_equal "USD", created_account.currency
|
||||||
|
assert_equal 5.5, created_account.loan.interest_rate
|
||||||
|
assert_equal 60, created_account.loan.term_months
|
||||||
|
assert_equal "fixed", created_account.loan.rate_type
|
||||||
|
|
||||||
|
assert_redirected_to account_path(created_account)
|
||||||
|
assert_equal "Loan created successfully", flash[:notice]
|
||||||
|
assert_enqueued_with(job: AccountSyncJob)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates loan" do
|
||||||
|
assert_no_difference [ "Account.count", "Loan.count" ] do
|
||||||
|
patch loan_path(@account), params: {
|
||||||
|
account: {
|
||||||
|
name: "Updated Loan",
|
||||||
|
balance: 45000,
|
||||||
|
currency: "USD",
|
||||||
|
accountable_type: "Loan",
|
||||||
|
accountable_attributes: {
|
||||||
|
id: @account.accountable_id,
|
||||||
|
interest_rate: 4.5,
|
||||||
|
term_months: 48,
|
||||||
|
rate_type: "fixed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
@account.reload
|
||||||
|
|
||||||
|
assert_equal "Updated Loan", @account.name
|
||||||
|
assert_equal 45000, @account.balance
|
||||||
|
assert_equal 4.5, @account.loan.interest_rate
|
||||||
|
assert_equal 48, @account.loan.term_months
|
||||||
|
assert_equal "fixed", @account.loan.rate_type
|
||||||
|
|
||||||
|
assert_redirected_to account_path(@account)
|
||||||
|
assert_equal "Loan updated successfully", flash[:notice]
|
||||||
|
assert_enqueued_with(job: AccountSyncJob)
|
||||||
|
end
|
||||||
|
end
|
|
@ -47,7 +47,7 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
||||||
end
|
end
|
||||||
|
|
||||||
test "updates property" do
|
test "updates property" do
|
||||||
assert_no_difference [ "Account.count", "Property.count", "Account::Valuation.count", "Account::Entry.count" ] do
|
assert_no_difference [ "Account.count", "Property.count" ] do
|
||||||
patch property_path(@account), params: {
|
patch property_path(@account), params: {
|
||||||
account: {
|
account: {
|
||||||
name: "Updated Property",
|
name: "Updated Property",
|
||||||
|
|
|
@ -44,7 +44,7 @@ class VehiclesControllerTest < ActionDispatch::IntegrationTest
|
||||||
end
|
end
|
||||||
|
|
||||||
test "updates vehicle" do
|
test "updates vehicle" do
|
||||||
assert_no_difference [ "Account.count", "Vehicle.count", "Account::Valuation.count", "Account::Entry.count" ] do
|
assert_no_difference [ "Account.count", "Vehicle.count" ] do
|
||||||
patch vehicle_path(@account), params: {
|
patch vehicle_path(@account), params: {
|
||||||
account: {
|
account: {
|
||||||
name: "Updated Vehicle",
|
name: "Updated Vehicle",
|
||||||
|
|
8
test/fixtures/credit_cards.yml
vendored
8
test/fixtures/credit_cards.yml
vendored
|
@ -1 +1,7 @@
|
||||||
one: { }
|
one:
|
||||||
|
available_credit: 5000.00
|
||||||
|
minimum_payment: 100.00
|
||||||
|
apr: 18.99
|
||||||
|
expiration_date: <%= 4.years.from_now.to_date %>
|
||||||
|
annual_fee: 95.00
|
||||||
|
|
5
test/fixtures/loans.yml
vendored
5
test/fixtures/loans.yml
vendored
|
@ -1 +1,4 @@
|
||||||
one: { }
|
one:
|
||||||
|
interest_rate: 3.5
|
||||||
|
term_months: 360
|
||||||
|
rate_type: fixed
|
||||||
|
|
|
@ -1,7 +1,18 @@
|
||||||
require "test_helper"
|
require "test_helper"
|
||||||
|
|
||||||
class LoanTest < ActiveSupport::TestCase
|
class LoanTest < ActiveSupport::TestCase
|
||||||
# test "the truth" do
|
test "calculates correct monthly payment for fixed rate loan" do
|
||||||
# assert true
|
loan_account = Account.create! \
|
||||||
# end
|
family: families(:dylan_family),
|
||||||
|
name: "Mortgage Loan",
|
||||||
|
balance: 500000,
|
||||||
|
currency: "USD",
|
||||||
|
accountable: Loan.create!(
|
||||||
|
interest_rate: 3.5,
|
||||||
|
term_months: 360,
|
||||||
|
rate_type: "fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_equal 2245, loan_account.loan.monthly_payment.amount
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,13 +22,13 @@ class AccountsTest < ApplicationSystemTestCase
|
||||||
|
|
||||||
test "can create property account" do
|
test "can create property account" do
|
||||||
assert_account_created "Property" do
|
assert_account_created "Property" do
|
||||||
fill_in "Year built (optional)", with: 2005
|
fill_in "Year built", with: 2005
|
||||||
fill_in "Area value (optional)", with: 2250
|
fill_in "Area value", with: 2250
|
||||||
fill_in "Address line 1", with: "123 Main St"
|
fill_in "Address line 1", with: "123 Main St"
|
||||||
fill_in "Address line 2", with: "Apt 4B"
|
fill_in "Address line 2", with: "Apt 4B"
|
||||||
fill_in "City", with: "San Francisco"
|
fill_in "City", with: "San Francisco"
|
||||||
fill_in "State", with: "CA"
|
fill_in "State", with: "CA"
|
||||||
fill_in "Postal code (optional)", with: "94101"
|
fill_in "Postal code", with: "94101"
|
||||||
fill_in "Country", with: "US"
|
fill_in "Country", with: "US"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -47,11 +47,21 @@ class AccountsTest < ApplicationSystemTestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can create credit card account" do
|
test "can create credit card account" do
|
||||||
assert_account_created("CreditCard")
|
assert_account_created "CreditCard" do
|
||||||
|
fill_in "Available credit", with: 1000
|
||||||
|
fill_in "Minimum payment", with: 25
|
||||||
|
fill_in "APR", with: 15.25
|
||||||
|
fill_in "Expiration date", with: 1.year.from_now.to_date
|
||||||
|
fill_in "Annual fee", with: 100
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can create loan account" do
|
test "can create loan account" do
|
||||||
assert_account_created("Loan")
|
assert_account_created "Loan" do
|
||||||
|
fill_in "Interest rate", with: 5.25
|
||||||
|
select "Fixed", from: "Rate type"
|
||||||
|
fill_in "Term (months)", with: 360
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can create other liability account" do
|
test "can create other liability account" do
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue