mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-31 11:09:39 +02:00
Clean up account creational methods
This commit is contained in:
parent
a7cd046563
commit
3b6a5a573f
6 changed files with 313 additions and 135 deletions
|
@ -128,6 +128,15 @@ class Account < ApplicationRecord
|
|||
Account::BalanceUpdater.new(self, balance:, currency:, date:, notes:).update
|
||||
end
|
||||
|
||||
def update_currency!(new_currency)
|
||||
raise "Currency cannot be changed" if linked?
|
||||
|
||||
transaction do
|
||||
update!(currency: new_currency)
|
||||
entries.valuations.update_all(currency: new_currency)
|
||||
end
|
||||
end
|
||||
|
||||
def update_current_balance(balance:, cash_balance:)
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Family < ApplicationRecord
|
||||
include PlaidConnectable, Syncable, AutoTransferMatchable, Subscribeable
|
||||
include PlaidConnectable, Syncable, AutoTransferMatchable, Subscribeable, AccountCreatable
|
||||
|
||||
DATE_FORMATS = [
|
||||
[ "MM-DD-YYYY", "%m-%d-%Y" ],
|
||||
|
@ -116,74 +116,4 @@ class Family < ApplicationRecord
|
|||
def self_hoster?
|
||||
Rails.application.config.app_mode.self_hosted?
|
||||
end
|
||||
|
||||
def create_property_account!(name:, current_value:, purchase_price: nil, purchase_date: nil, currency: nil)
|
||||
Family.transaction do
|
||||
property = accounts.create!(
|
||||
name: name,
|
||||
balance: current_value,
|
||||
cash_balance: 0,
|
||||
currency: currency.presence || self.currency,
|
||||
accountable: Property.new
|
||||
)
|
||||
|
||||
property.set_or_update_opening_balance!(
|
||||
balance: purchase_price || 0,
|
||||
cash_balance: 0,
|
||||
date: purchase_date
|
||||
)
|
||||
|
||||
property
|
||||
end
|
||||
end
|
||||
|
||||
def create_vehicle_account(current_value:, purchase_price: nil, purchase_date: nil, currency: nil)
|
||||
# TODO
|
||||
end
|
||||
|
||||
def create_depository_account(current_balance:, opening_date: nil, currency: nil)
|
||||
# TODO
|
||||
end
|
||||
|
||||
# Investment account values are built up by adding holdings / trades, not by initializing a "balance"
|
||||
def create_investment_account(currency: nil)
|
||||
# TODO
|
||||
end
|
||||
|
||||
def create_other_asset_account(current_value:, purchase_price: nil, purchase_date: nil, currency: nil)
|
||||
# TODO
|
||||
end
|
||||
|
||||
def create_other_liability_account(current_debt:, original_debt: nil, origination_date: nil, currency: nil)
|
||||
# TODO
|
||||
end
|
||||
|
||||
# For now, crypto accounts are very simple; we just track overall value
|
||||
def create_crypto_account(current_value:, currency: nil)
|
||||
# TODO
|
||||
end
|
||||
|
||||
def create_credit_card_account(current_debt:, opening_date: nil, currency: nil)
|
||||
# TODO
|
||||
end
|
||||
|
||||
def create_loan_account(current_principal:, original_principal: nil, origination_date: nil, currency: nil)
|
||||
# TODO
|
||||
end
|
||||
|
||||
def link_depository_account
|
||||
# TODO
|
||||
end
|
||||
|
||||
def link_investment_account
|
||||
# TODO
|
||||
end
|
||||
|
||||
def link_credit_card_account
|
||||
# TODO
|
||||
end
|
||||
|
||||
def link_loan_account
|
||||
# TODO
|
||||
end
|
||||
end
|
||||
|
|
150
app/models/family/account_creatable.rb
Normal file
150
app/models/family/account_creatable.rb
Normal file
|
@ -0,0 +1,150 @@
|
|||
module Family::AccountCreatable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def create_property_account!(name:, current_value:, purchase_price: nil, purchase_date: nil, currency: nil)
|
||||
create_manual_account!(
|
||||
name: name,
|
||||
balance: current_value,
|
||||
cash_balance: 0,
|
||||
accountable_type: Property,
|
||||
opening_balance: purchase_price,
|
||||
opening_date: purchase_date,
|
||||
currency: currency
|
||||
)
|
||||
end
|
||||
|
||||
def create_vehicle_account!(name:, current_value:, purchase_price: nil, purchase_date: nil, currency: nil)
|
||||
create_manual_account!(
|
||||
name: name,
|
||||
balance: current_value,
|
||||
cash_balance: 0,
|
||||
accountable_type: Vehicle,
|
||||
opening_balance: purchase_price,
|
||||
opening_date: purchase_date,
|
||||
currency: currency
|
||||
)
|
||||
end
|
||||
|
||||
def create_depository_account!(name:, current_balance:, opening_date: nil, currency: nil)
|
||||
create_manual_account!(
|
||||
name: name,
|
||||
balance: current_balance,
|
||||
cash_balance: current_balance,
|
||||
accountable_type: Depository,
|
||||
opening_date: opening_date,
|
||||
currency: currency
|
||||
)
|
||||
end
|
||||
|
||||
# Investment account values are built up by adding holdings / trades, not by initializing a "balance"
|
||||
def create_investment_account!(name:, currency: nil)
|
||||
create_manual_account!(
|
||||
name: name,
|
||||
balance: 0,
|
||||
cash_balance: 0,
|
||||
accountable_type: Investment,
|
||||
opening_balance: 0, # Investment accounts start empty
|
||||
opening_cash_balance: 0,
|
||||
currency: currency
|
||||
)
|
||||
end
|
||||
|
||||
def create_other_asset_account!(name:, current_value:, purchase_price: nil, purchase_date: nil, currency: nil)
|
||||
create_manual_account!(
|
||||
name: name,
|
||||
balance: current_value,
|
||||
cash_balance: 0,
|
||||
accountable_type: OtherAsset,
|
||||
opening_balance: purchase_price,
|
||||
opening_date: purchase_date,
|
||||
currency: currency
|
||||
)
|
||||
end
|
||||
|
||||
def create_other_liability_account!(name:, current_debt:, original_debt: nil, origination_date: nil, currency: nil)
|
||||
create_manual_account!(
|
||||
name: name,
|
||||
balance: current_debt,
|
||||
cash_balance: 0,
|
||||
accountable_type: OtherLiability,
|
||||
opening_balance: original_debt,
|
||||
opening_date: origination_date,
|
||||
currency: currency
|
||||
)
|
||||
end
|
||||
|
||||
# For now, crypto accounts are very simple; we just track overall value
|
||||
def create_crypto_account!(name:, current_value:, currency: nil)
|
||||
create_manual_account!(
|
||||
name: name,
|
||||
balance: current_value,
|
||||
cash_balance: current_value,
|
||||
accountable_type: Crypto,
|
||||
opening_balance: current_value,
|
||||
opening_cash_balance: current_value,
|
||||
currency: currency
|
||||
)
|
||||
end
|
||||
|
||||
def create_credit_card_account!(name:, current_debt:, opening_date: nil, currency: nil)
|
||||
create_manual_account!(
|
||||
name: name,
|
||||
balance: current_debt,
|
||||
cash_balance: 0,
|
||||
accountable_type: CreditCard,
|
||||
opening_balance: 0, # Credit cards typically start with no debt
|
||||
opening_date: opening_date,
|
||||
currency: currency
|
||||
)
|
||||
end
|
||||
|
||||
def create_loan_account!(name:, current_principal:, original_principal: nil, origination_date: nil, currency: nil)
|
||||
create_manual_account!(
|
||||
name: name,
|
||||
balance: current_principal,
|
||||
cash_balance: 0,
|
||||
accountable_type: Loan,
|
||||
opening_balance: original_principal,
|
||||
opening_date: origination_date,
|
||||
currency: currency
|
||||
)
|
||||
end
|
||||
|
||||
def link_depository_account
|
||||
# TODO
|
||||
end
|
||||
|
||||
def link_investment_account
|
||||
# TODO
|
||||
end
|
||||
|
||||
def link_credit_card_account
|
||||
# TODO
|
||||
end
|
||||
|
||||
def link_loan_account
|
||||
# TODO
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_manual_account!(name:, balance:, cash_balance:, accountable_type:, opening_balance: nil, opening_cash_balance: nil, opening_date: nil, currency: nil)
|
||||
Family.transaction do
|
||||
account = accounts.create!(
|
||||
name: name,
|
||||
balance: balance,
|
||||
cash_balance: cash_balance,
|
||||
currency: currency.presence || self.currency,
|
||||
accountable: accountable_type.new
|
||||
)
|
||||
|
||||
account.set_or_update_opening_balance!(
|
||||
balance: opening_balance || balance,
|
||||
cash_balance: opening_cash_balance || cash_balance,
|
||||
date: opening_date
|
||||
)
|
||||
|
||||
account
|
||||
end
|
||||
end
|
||||
end
|
|
@ -35,9 +35,10 @@ class AccountTest < ActiveSupport::TestCase
|
|||
# Currency updates earn their own method because updating an account currency incurs
|
||||
# side effects like recalculating balances, etc.
|
||||
test "can update the account currency" do
|
||||
@account.update_currency("EUR")
|
||||
@account.update_currency!("EUR")
|
||||
|
||||
assert_equal "EUR", @account.currency
|
||||
assert_equal "EUR", @account.entries.valuations.first.currency
|
||||
end
|
||||
|
||||
# If a user has an opening balance (valuation) for their manual account and has 1+ transactions, the intent of
|
||||
|
|
151
test/models/family/account_creatable_test.rb
Normal file
151
test/models/family/account_creatable_test.rb
Normal file
|
@ -0,0 +1,151 @@
|
|||
require "test_helper"
|
||||
|
||||
class Family::AccountCreatableTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@family = families(:dylan_family)
|
||||
end
|
||||
|
||||
test "creates manual property account" do
|
||||
account = @family.create_property_account!(
|
||||
name: "My House",
|
||||
current_value: 450000,
|
||||
purchase_price: 400000,
|
||||
purchase_date: 1.year.ago.to_date
|
||||
)
|
||||
|
||||
assert_opening_valuation(account: account, balance: 400000)
|
||||
assert_account_created_with(account: account, name: "My House", balance: 450000, cash_balance: 0)
|
||||
end
|
||||
|
||||
test "creates manual vehicle account" do
|
||||
account = @family.create_vehicle_account!(
|
||||
name: "My Car",
|
||||
current_value: 25000,
|
||||
purchase_price: 30000,
|
||||
purchase_date: 2.years.ago.to_date
|
||||
)
|
||||
|
||||
assert_opening_valuation(account: account, balance: 30000)
|
||||
assert_account_created_with(account: account, name: "My Car", balance: 25000, cash_balance: 0)
|
||||
end
|
||||
|
||||
test "creates manual depository account" do
|
||||
account = @family.create_depository_account!(
|
||||
name: "My Checking",
|
||||
current_balance: 5000,
|
||||
opening_date: 1.year.ago.to_date
|
||||
)
|
||||
|
||||
assert_opening_valuation(account: account, balance: 5000, cash_balance: 5000)
|
||||
assert_account_created_with(account: account, name: "My Checking", balance: 5000, cash_balance: 5000)
|
||||
end
|
||||
|
||||
test "creates manual investment account" do
|
||||
account = @family.create_investment_account!(
|
||||
name: "My Brokerage"
|
||||
)
|
||||
|
||||
assert_opening_valuation(account: account, balance: 0, cash_balance: 0)
|
||||
assert_account_created_with(account: account, name: "My Brokerage", balance: 0, cash_balance: 0)
|
||||
end
|
||||
|
||||
test "creates manual other asset account" do
|
||||
account = @family.create_other_asset_account!(
|
||||
name: "Collectible",
|
||||
current_value: 10000,
|
||||
purchase_price: 5000,
|
||||
purchase_date: 3.years.ago.to_date
|
||||
)
|
||||
|
||||
assert_opening_valuation(account: account, balance: 5000)
|
||||
assert_account_created_with(account: account, name: "Collectible", balance: 10000, cash_balance: 0)
|
||||
end
|
||||
|
||||
test "creates manual other liability account" do
|
||||
account = @family.create_other_liability_account!(
|
||||
name: "Personal Loan",
|
||||
current_debt: 5000,
|
||||
original_debt: 10000,
|
||||
origination_date: 2.years.ago.to_date
|
||||
)
|
||||
|
||||
assert_opening_valuation(account: account, balance: 10000)
|
||||
assert_account_created_with(account: account, name: "Personal Loan", balance: 5000, cash_balance: 0)
|
||||
end
|
||||
|
||||
test "creates manual crypto account" do
|
||||
account = @family.create_crypto_account!(
|
||||
name: "Bitcoin Wallet",
|
||||
current_value: 50000
|
||||
)
|
||||
|
||||
assert_opening_valuation(account: account, balance: 50000, cash_balance: 50000)
|
||||
assert_account_created_with(account: account, name: "Bitcoin Wallet", balance: 50000, cash_balance: 50000)
|
||||
end
|
||||
|
||||
test "creates manual credit card account" do
|
||||
account = @family.create_credit_card_account!(
|
||||
name: "Visa Card",
|
||||
current_debt: 2000,
|
||||
opening_date: 6.months.ago.to_date
|
||||
)
|
||||
|
||||
assert_opening_valuation(account: account, balance: 0, cash_balance: 0)
|
||||
assert_account_created_with(account: account, name: "Visa Card", balance: 2000, cash_balance: 0)
|
||||
end
|
||||
|
||||
test "creates manual loan account" do
|
||||
account = @family.create_loan_account!(
|
||||
name: "Home Mortgage",
|
||||
current_principal: 200000,
|
||||
original_principal: 250000,
|
||||
origination_date: 5.years.ago.to_date
|
||||
)
|
||||
|
||||
assert_opening_valuation(account: account, balance: 250000)
|
||||
assert_account_created_with(account: account, name: "Home Mortgage", balance: 200000, cash_balance: 0)
|
||||
end
|
||||
|
||||
test "creates property account without purchase price" do
|
||||
account = @family.create_property_account!(
|
||||
name: "My House",
|
||||
current_value: 500000
|
||||
)
|
||||
|
||||
assert_opening_valuation(account: account, balance: 500000)
|
||||
assert_account_created_with(account: account, name: "My House", balance: 500000, cash_balance: 0)
|
||||
end
|
||||
|
||||
test "creates linked depository account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates linked investment account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates linked credit card account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates linked loan account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
private
|
||||
def assert_account_created_with(account:, name:, balance:, cash_balance:)
|
||||
assert_equal name, account.name
|
||||
assert_equal balance, account.balance
|
||||
assert_equal cash_balance, account.cash_balance
|
||||
end
|
||||
|
||||
def assert_opening_valuation(account:, balance:, cash_balance: 0)
|
||||
valuations = account.valuations
|
||||
assert_equal 1, valuations.count
|
||||
|
||||
opening_valuation = valuations.first
|
||||
assert_equal "opening_anchor", opening_valuation.kind
|
||||
assert_equal balance, opening_valuation.balance
|
||||
assert_equal cash_balance, opening_valuation.cash_balance
|
||||
end
|
||||
end
|
|
@ -6,67 +6,4 @@ class FamilyTest < ActiveSupport::TestCase
|
|||
def setup
|
||||
@syncable = @family = families(:dylan_family)
|
||||
end
|
||||
|
||||
test "creates manual property account" do
|
||||
account = @family.create_property_account!(
|
||||
name: "My House",
|
||||
current_value: 450000,
|
||||
purchase_price: 400000,
|
||||
purchase_date: 1.year.ago.to_date
|
||||
)
|
||||
|
||||
valuations = account.valuations
|
||||
assert_equal 1, valuations.count
|
||||
assert_equal "opening_anchor", valuations.first.kind
|
||||
assert_equal 400000, valuations.first.balance
|
||||
assert_equal 0, valuations.first.cash_balance
|
||||
|
||||
assert_equal "My House", account.name
|
||||
assert_equal 450000, account.balance
|
||||
assert_equal 0, account.cash_balance
|
||||
end
|
||||
|
||||
test "creates manual vehicle account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates manual depository account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates manual investment account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates manual other asset or liability account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates manual crypto account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates manual credit card account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates manual loan account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates linked depository account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates linked investment account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates linked credit card account" do
|
||||
# TODO
|
||||
end
|
||||
|
||||
test "creates linked loan account" do
|
||||
# TODO
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue