Add tax treatment classification for investment accounts with international support (#693)

* Add tax treatment support for accounts, investments, and cryptos

* Replace hardcoded region labels with I18n translations

* Add I18n support for subtype labels with fallback to hardcoded values

* fixed schema

---------

Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
This commit is contained in:
LPW
2026-01-18 11:29:02 -05:00
committed by GitHub
parent 6ec03e93f4
commit 64dc5c2fb8
14 changed files with 488 additions and 23 deletions

View File

@@ -89,4 +89,70 @@ class AccountTest < ActiveSupport::TestCase
assert_equal "Investments", account.short_subtype_label
assert_equal "Investments", account.long_subtype_label
end
# Tax treatment tests (TaxTreatable concern)
test "tax_treatment delegates to accountable for Investment" do
investment = Investment.new(subtype: "401k")
account = @family.accounts.create!(
name: "Test 401k",
balance: 1000,
currency: "USD",
accountable: investment
)
assert_equal :tax_deferred, account.tax_treatment
assert_equal I18n.t("accounts.tax_treatments.tax_deferred"), account.tax_treatment_label
end
test "tax_treatment delegates to accountable for Crypto" do
crypto = Crypto.new(tax_treatment: :taxable)
account = @family.accounts.create!(
name: "Test Crypto",
balance: 500,
currency: "USD",
accountable: crypto
)
assert_equal :taxable, account.tax_treatment
assert_equal I18n.t("accounts.tax_treatments.taxable"), account.tax_treatment_label
end
test "tax_treatment returns nil for non-investment accounts" do
# Depository accounts don't have tax_treatment
assert_nil @account.tax_treatment
assert_nil @account.tax_treatment_label
end
test "tax_advantaged? returns true for tax-advantaged accounts" do
investment = Investment.new(subtype: "401k")
account = @family.accounts.create!(
name: "Test 401k",
balance: 1000,
currency: "USD",
accountable: investment
)
assert account.tax_advantaged?
assert_not account.taxable?
end
test "tax_advantaged? returns false for taxable accounts" do
investment = Investment.new(subtype: "brokerage")
account = @family.accounts.create!(
name: "Test Brokerage",
balance: 1000,
currency: "USD",
accountable: investment
)
assert_not account.tax_advantaged?
assert account.taxable?
end
test "taxable? returns true for accounts without tax_treatment" do
# Depository accounts
assert @account.taxable?
assert_not @account.tax_advantaged?
end
end

View File

@@ -0,0 +1,30 @@
require "test_helper"
class CryptoTest < ActiveSupport::TestCase
test "tax_treatment defaults to taxable" do
crypto = Crypto.new
assert_equal "taxable", crypto.tax_treatment
end
test "tax_treatment can be set to tax_deferred" do
crypto = Crypto.new(tax_treatment: :tax_deferred)
assert_equal "tax_deferred", crypto.tax_treatment
end
test "tax_treatment can be set to tax_exempt" do
crypto = Crypto.new(tax_treatment: :tax_exempt)
assert_equal "tax_exempt", crypto.tax_treatment
end
test "tax_treatment enum provides predicate methods" do
crypto = Crypto.new(tax_treatment: :taxable)
assert crypto.taxable?
assert_not crypto.tax_deferred?
assert_not crypto.tax_exempt?
crypto.tax_treatment = :tax_deferred
assert_not crypto.taxable?
assert crypto.tax_deferred?
assert_not crypto.tax_exempt?
end
end

View File

@@ -0,0 +1,139 @@
require "test_helper"
class InvestmentTest < ActiveSupport::TestCase
# Tax treatment derivation tests
test "tax_treatment returns tax_deferred for US retirement accounts" do
%w[401k 403b 457b tsp ira sep_ira simple_ira].each do |subtype|
investment = Investment.new(subtype: subtype)
assert_equal :tax_deferred, investment.tax_treatment, "Expected #{subtype} to be tax_deferred"
end
end
test "tax_treatment returns tax_exempt for Roth accounts" do
%w[roth_401k roth_ira].each do |subtype|
investment = Investment.new(subtype: subtype)
assert_equal :tax_exempt, investment.tax_treatment, "Expected #{subtype} to be tax_exempt"
end
end
test "tax_treatment returns tax_advantaged for special accounts" do
%w[529_plan hsa].each do |subtype|
investment = Investment.new(subtype: subtype)
assert_equal :tax_advantaged, investment.tax_treatment, "Expected #{subtype} to be tax_advantaged"
end
end
test "tax_treatment returns taxable for standard accounts" do
%w[brokerage mutual_fund angel trust ugma utma other].each do |subtype|
investment = Investment.new(subtype: subtype)
assert_equal :taxable, investment.tax_treatment, "Expected #{subtype} to be taxable"
end
end
test "tax_treatment returns taxable for nil subtype" do
investment = Investment.new(subtype: nil)
assert_equal :taxable, investment.tax_treatment
end
test "tax_treatment returns taxable for unknown subtype" do
investment = Investment.new(subtype: "unknown_type")
assert_equal :taxable, investment.tax_treatment
end
# UK account types
test "tax_treatment returns tax_exempt for UK ISA accounts" do
%w[isa lisa].each do |subtype|
investment = Investment.new(subtype: subtype)
assert_equal :tax_exempt, investment.tax_treatment, "Expected #{subtype} to be tax_exempt"
end
end
test "tax_treatment returns tax_deferred for UK pension accounts" do
%w[sipp workplace_pension_uk].each do |subtype|
investment = Investment.new(subtype: subtype)
assert_equal :tax_deferred, investment.tax_treatment, "Expected #{subtype} to be tax_deferred"
end
end
# Canadian account types
test "tax_treatment returns tax_deferred for Canadian retirement accounts" do
%w[rrsp lira rrif].each do |subtype|
investment = Investment.new(subtype: subtype)
assert_equal :tax_deferred, investment.tax_treatment, "Expected #{subtype} to be tax_deferred"
end
end
test "tax_treatment returns tax_exempt for Canadian TFSA" do
investment = Investment.new(subtype: "tfsa")
assert_equal :tax_exempt, investment.tax_treatment
end
test "tax_treatment returns tax_advantaged for Canadian RESP" do
investment = Investment.new(subtype: "resp")
assert_equal :tax_advantaged, investment.tax_treatment
end
# Australian account types
test "tax_treatment returns tax_deferred for Australian super accounts" do
%w[super smsf].each do |subtype|
investment = Investment.new(subtype: subtype)
assert_equal :tax_deferred, investment.tax_treatment, "Expected #{subtype} to be tax_deferred"
end
end
# European account types
test "tax_treatment returns tax_deferred for European pension accounts" do
%w[pillar_3a riester].each do |subtype|
investment = Investment.new(subtype: subtype)
assert_equal :tax_deferred, investment.tax_treatment, "Expected #{subtype} to be tax_deferred"
end
end
test "tax_treatment returns tax_advantaged for French PEA" do
investment = Investment.new(subtype: "pea")
assert_equal :tax_advantaged, investment.tax_treatment
end
# Generic account types
test "tax_treatment returns tax_deferred for generic pension and retirement" do
%w[pension retirement].each do |subtype|
investment = Investment.new(subtype: subtype)
assert_equal :tax_deferred, investment.tax_treatment, "Expected #{subtype} to be tax_deferred"
end
end
# Subtype metadata tests
test "all subtypes have required metadata keys" do
Investment::SUBTYPES.each do |key, metadata|
assert metadata.key?(:short), "Subtype #{key} missing :short key"
assert metadata.key?(:long), "Subtype #{key} missing :long key"
assert metadata.key?(:tax_treatment), "Subtype #{key} missing :tax_treatment key"
assert metadata.key?(:region), "Subtype #{key} missing :region key"
end
end
test "all subtypes have valid tax_treatment values" do
valid_treatments = %i[taxable tax_deferred tax_exempt tax_advantaged]
Investment::SUBTYPES.each do |key, metadata|
assert_includes valid_treatments, metadata[:tax_treatment],
"Subtype #{key} has invalid tax_treatment: #{metadata[:tax_treatment]}"
end
end
test "all subtypes have valid region values" do
valid_regions = [ "us", "uk", "ca", "au", "eu", nil ]
Investment::SUBTYPES.each do |key, metadata|
assert_includes valid_regions, metadata[:region],
"Subtype #{key} has invalid region: #{metadata[:region]}"
end
end
end