Files
sure/app/models/depository.rb
galuis116 61a235f765 fix(family): include HSA depository accounts in tax-advantaged exclusion (#2004)
* fix(family): include HSA depository accounts in tax-advantaged exclusion

`Family#tax_advantaged_account_ids` is the ID set the budget engine uses
to exclude tax-advantaged account activity from income / expense /
cashflow totals. PR #724 originated this method and explicitly listed HSA
in scope ("401k, IRA, HSA, Roth IRA, etc."), but the implementation only
joined `investments` and `cryptos`. `Depository::SUBTYPES["hsa"]` already
exists and Plaid routes `depository.hsa` accounts to `Depository` (not
`Investment`) via `PlaidAccount::TypeMappable`, so HSA cash accounts were
silently absent from the filter and HSA contributions/withdrawals showed
up in household expense totals.

- Add `Depository::TAX_ADVANTAGED_SUBTYPES = %w[hsa]` + a `tax_treatment`
  instance method (mirrors `Investment#tax_treatment`).
  `TaxTreatable#tax_advantaged?` picks it up via the existing `respond_to?`
  check, so `Account#tax_advantaged?` now flips to true for HSA depositories
  without touching the concern.
- Extract `Family#tax_advantaged_depository_account_ids` (private) that
  joins `depositories` and filters by `Depository::TAX_ADVANTAGED_SUBTYPES`,
  mirroring the existing `investment_ids` / `crypto_ids` extraction style.
  Append it to the union in `tax_advantaged_account_ids`.

Behavior change is scoped: HSA depositories now exit the budget engine via
the same path as 401k / IRA / Roth IRA. Non-HSA depositories continue to
report `tax_treatment: :taxable` (was `nil`), so `Account#taxable?` returns
true for them via the existing `== :taxable` clause — no expense-total
change for Checking / Savings / CD / Money Market.

Tests:
- `test/models/account_test.rb` — rewrite "tax_treatment returns nil for
  non-investment accounts" (was implicitly testing the bug) into two tests:
  one asserting `:taxable` for non-HSA depositories and a new sibling
  asserting `nil` for accountables that genuinely lack `tax_treatment`
  (CreditCard). Add an HSA-depository test asserting `tax_advantaged?`.
- `test/models/income_statement_test.rb` — new test asserting an HSA
  depository is included in `tax_advantaged_account_ids` and a `savings`
  depository is not.

No schema migration, no controller change, no provider integration change.

* [200~fix(family): return nil for non-HSA depository tax_treatment
2026-05-31 16:24:01 +02:00

50 lines
1.8 KiB
Ruby

class Depository < ApplicationRecord
include Accountable
DEFAULT_SUBTYPE = "checking"
SUBTYPES = {
"checking" => { short: "Checking", long: "Checking" },
"savings" => { short: "Savings", long: "Savings" },
"hsa" => { short: "HSA", long: "Health Savings Account" },
"cd" => { short: "CD", long: "Certificate of Deposit" },
"money_market" => { short: "MM", long: "Money Market" }
}.freeze
# Depository subtypes that carry tax-advantaged treatment in the budget /
# cashflow / income-statement filters (`Family#tax_advantaged_account_ids`,
# `TaxTreatable#tax_advantaged?`). HSA cash sits here because Plaid routes
# `depository.hsa` to `Depository` (not `Investment`) via
# `PlaidAccount::TypeMappable`, so a real-world Plaid-linked HSA cash account
# was previously invisible to the tax-advantaged filter PR #724 introduced.
TAX_ADVANTAGED_SUBTYPES = %w[hsa].freeze
# `TaxTreatable` (the `Account` concern) reads this via `respond_to?` so
# adding it here transparently flips `Account#tax_advantaged?` for HSA
# depositories without touching the concern itself.
#
# Returns `nil` (not `:taxable`) for ordinary depository subtypes. `nil`
# already reads as taxable everywhere it matters: `TaxTreatable#taxable?`
# treats `nil` as taxable and `#tax_advantaged?` excludes it. Returning
# `nil` also keeps `tax_treatment.present?` false so the header tax badge
# (`app/views/accounts/show/_header.html.erb`) stays hidden on checking,
# savings, CD, and money-market accounts that never displayed it before.
def tax_treatment
:tax_advantaged if TAX_ADVANTAGED_SUBTYPES.include?(subtype)
end
class << self
def color
"#875BF7"
end
def classification
"asset"
end
def icon
"landmark"
end
end
end