diff --git a/app/models/account.rb b/app/models/account.rb index 6317fb94..f07b442f 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -155,6 +155,16 @@ class Account < ApplicationRecord first_valuation&.amount_money || balance_money end + # Get short version of the subtype label + def short_subtype_label + accountable_class.short_subtype_label_for(subtype) || accountable_class.display_name + end + + # Get long version of the subtype label + def long_subtype_label + accountable_class.long_subtype_label_for(subtype) || accountable_class.display_name + end + private def sync_balances strategy = linked? ? :reverse : :forward diff --git a/app/models/balance_sheet.rb b/app/models/balance_sheet.rb index f6b07f16..21b4aeca 100644 --- a/app/models/balance_sheet.rb +++ b/app/models/balance_sheet.rb @@ -59,6 +59,7 @@ class BalanceSheet account.define_singleton_method(:weight) do classification_total.zero? ? 0 : account.converted_balance / classification_total.to_d * 100 end + account end.sort_by(&:weight).reverse ) diff --git a/app/models/concerns/accountable.rb b/app/models/concerns/accountable.rb index beb3cf23..9d0ecb34 100644 --- a/app/models/concerns/accountable.rb +++ b/app/models/concerns/accountable.rb @@ -3,6 +3,9 @@ module Accountable TYPES = %w[Depository Investment Crypto Property Vehicle OtherAsset CreditCard Loan OtherLiability] + # Define empty hash to ensure all accountables have this defined + SUBTYPES = {}.freeze + def self.from_type(type) return nil unless TYPES.include?(type) type.constantize @@ -27,6 +30,24 @@ module Accountable raise NotImplementedError, "Accountable must implement #color" end + # Given a subtype, look up the label for this accountable type + def subtype_label_for(subtype, format: :short) + return nil if subtype.nil? + + label_type = format == :long ? :long : :short + self::SUBTYPES[subtype]&.fetch(label_type, nil) + end + + # Convenience method for getting the short label + def short_subtype_label_for(subtype) + subtype_label_for(subtype, format: :short) + end + + # Convenience method for getting the long label + def long_subtype_label_for(subtype) + subtype_label_for(subtype, format: :long) + end + def favorable_direction classification == "asset" ? "up" : "down" end diff --git a/app/models/depository.rb b/app/models/depository.rb index 0e03547c..577a061c 100644 --- a/app/models/depository.rb +++ b/app/models/depository.rb @@ -1,10 +1,10 @@ class Depository < ApplicationRecord include Accountable - SUBTYPES = [ - [ "Checking", "checking" ], - [ "Savings", "savings" ] - ].freeze + SUBTYPES = { + "checking" => { short: "Checking", long: "Checking" }, + "savings" => { short: "Savings", long: "Savings" } + }.freeze class << self def display_name diff --git a/app/models/investment.rb b/app/models/investment.rb index 1dabae3e..6b2518ce 100644 --- a/app/models/investment.rb +++ b/app/models/investment.rb @@ -1,20 +1,20 @@ class Investment < ApplicationRecord include Accountable - SUBTYPES = [ - [ "Brokerage", "brokerage" ], - [ "Pension", "pension" ], - [ "Retirement", "retirement" ], - [ "401(k)", "401k" ], - [ "Traditional 401(k)", "traditional_401k" ], - [ "Roth 401(k)", "roth_401k" ], - [ "529 Plan", "529_plan" ], - [ "Health Savings Account", "hsa" ], - [ "Mutual Fund", "mutual_fund" ], - [ "Traditional IRA", "traditional_ira" ], - [ "Roth IRA", "roth_ira" ], - [ "Angel", "angel" ] - ].freeze + SUBTYPES = { + "brokerage" => { short: "Brokerage", long: "Brokerage" }, + "pension" => { short: "Pension", long: "Pension" }, + "retirement" => { short: "Retirement", long: "Retirement" }, + "401k" => { short: "401(k)", long: "401(k)" }, + "traditional_401k" => { short: "Traditional 401(k)", long: "Traditional 401(k)" }, + "roth_401k" => { short: "Roth 401(k)", long: "Roth 401(k)" }, + "529_plan" => { short: "529 Plan", long: "529 Plan" }, + "hsa" => { short: "HSA", long: "Health Savings Account" }, + "mutual_fund" => { short: "Mutual Fund", long: "Mutual Fund" }, + "traditional_ira" => { short: "Traditional IRA", long: "Traditional IRA" }, + "roth_ira" => { short: "Roth IRA", long: "Roth IRA" }, + "angel" => { short: "Angel", long: "Angel" } + }.freeze class << self def color diff --git a/app/models/property.rb b/app/models/property.rb index 42d2979e..a530f8b1 100644 --- a/app/models/property.rb +++ b/app/models/property.rb @@ -1,14 +1,14 @@ class Property < ApplicationRecord include Accountable - SUBTYPES = [ - [ "Single Family Home", "single_family_home" ], - [ "Multi-Family Home", "multi_family_home" ], - [ "Condominium", "condominium" ], - [ "Townhouse", "townhouse" ], - [ "Investment Property", "investment_property" ], - [ "Second Home", "second_home" ] - ] + SUBTYPES = { + "single_family_home" => { short: "Single Family Home", long: "Single Family Home" }, + "multi_family_home" => { short: "Multi-Family Home", long: "Multi-Family Home" }, + "condominium" => { short: "Condo", long: "Condominium" }, + "townhouse" => { short: "Townhouse", long: "Townhouse" }, + "investment_property" => { short: "Investment Property", long: "Investment Property" }, + "second_home" => { short: "Second Home", long: "Second Home" } + }.freeze has_one :address, as: :addressable, dependent: :destroy diff --git a/app/views/accounts/_account.html.erb b/app/views/accounts/_account.html.erb index afbd6fb3..07236d6c 100644 --- a/app/views/accounts/_account.html.erb +++ b/app/views/accounts/_account.html.erb @@ -17,6 +17,9 @@

<% else %> <%= link_to account.name, account, class: [(account.is_active ? "text-primary" : "text-subdued"), "text-sm font-medium hover:underline"], data: { turbo_frame: "_top" } %> + <% if account.long_subtype_label %> +

<%= account.long_subtype_label %>

+ <% end %> <% end %> diff --git a/app/views/accounts/_accountable_group.html.erb b/app/views/accounts/_accountable_group.html.erb index b9676a9a..3e2a26ee 100644 --- a/app/views/accounts/_accountable_group.html.erb +++ b/app/views/accounts/_accountable_group.html.erb @@ -24,7 +24,7 @@
<%= tag.p account.name, class: "text-sm text-primary font-medium mb-0.5 truncate" %> - <%= tag.p account.subtype&.humanize.presence || account_group.name, class: "text-sm text-secondary truncate" %> + <%= tag.p account.short_subtype_label, class: "text-sm text-secondary truncate" %>
diff --git a/app/views/depositories/_form.html.erb b/app/views/depositories/_form.html.erb index 1cb9d1aa..df853e8d 100644 --- a/app/views/depositories/_form.html.erb +++ b/app/views/depositories/_form.html.erb @@ -2,6 +2,6 @@ <%= render "accounts/form", account: account, url: url do |form| %> <%= form.select :subtype, - Depository::SUBTYPES, + Depository::SUBTYPES.map { |k, v| [v[:long], k] }, { label: true, prompt: t("depositories.form.subtype_prompt"), include_blank: t("depositories.form.none") } %> <% end %> diff --git a/app/views/investments/_form.html.erb b/app/views/investments/_form.html.erb index e6b2dc46..a9387af1 100644 --- a/app/views/investments/_form.html.erb +++ b/app/views/investments/_form.html.erb @@ -2,6 +2,6 @@ <%= render "accounts/form", account: account, url: url do |form| %> <%= form.select :subtype, - Investment::SUBTYPES, + Investment::SUBTYPES.map { |k, v| [v[:long], k] }, { label: true, prompt: t("investments.form.subtype_prompt"), include_blank: t("investments.form.none") } %> <% end %> diff --git a/app/views/properties/_form.html.erb b/app/views/properties/_form.html.erb index c26c71ba..61b6cb59 100644 --- a/app/views/properties/_form.html.erb +++ b/app/views/properties/_form.html.erb @@ -2,7 +2,7 @@ <%= render "accounts/form", account: account, url: url do |form| %> <%= form.select :subtype, - Property::SUBTYPES, + Property::SUBTYPES.map { |k, v| [v[:long], k] }, { label: true, prompt: t("properties.form.subtype_prompt"), include_blank: t("properties.form.none") } %>
diff --git a/test/models/account_test.rb b/test/models/account_test.rb index 4d10da21..c8eb9749 100644 --- a/test/models/account_test.rb +++ b/test/models/account_test.rb @@ -13,4 +13,22 @@ class AccountTest < ActiveSupport::TestCase @account.destroy end end + + test "gets short/long subtype label" do + account = @family.accounts.create!( + name: "Test Investment", + balance: 1000, + currency: "USD", + subtype: "hsa", + accountable: Investment.new + ) + + assert_equal "HSA", account.short_subtype_label + assert_equal "Health Savings Account", account.long_subtype_label + + # Test with nil subtype + account.update!(subtype: nil) + assert_equal "Investments", account.short_subtype_label + assert_equal "Investments", account.long_subtype_label + end end