Add investment activity detection, labels, and exclusions

- Introduced `InvestmentActivityDetector` to mark internal investment activity as excluded from cashflow and assign appropriate labels.
- Added `exclude_from_cashflow` flag to `entries` and `investment_activity_label` to `transactions` with migrations.
- Implemented rake tasks to backfill and clear investment activity labels.
- Updated `PlaidAccount::Investments::TransactionsProcessor` to map Plaid transaction types to labels.
- Included comprehensive test coverage for new functionality.
This commit is contained in:
Josh Waldrep
2026-01-10 19:48:04 -05:00
parent 70e7a5f2d6
commit 52588784d0
26 changed files with 1235 additions and 82 deletions

View File

@@ -47,8 +47,9 @@ class IncomeStatement::CategoryStats
er.to_currency = :target_currency
)
WHERE a.family_id = :family_id
AND t.kind NOT IN ('funds_movement', 'one_time', 'cc_payment')
AND t.kind NOT IN ('funds_movement', 'one_time', 'cc_payment', 'investment_contribution')
AND ae.excluded = false
AND ae.exclude_from_cashflow = false
AND (t.extra -> 'simplefin' ->> 'pending')::boolean IS DISTINCT FROM true
AND (t.extra -> 'plaid' ->> 'pending')::boolean IS DISTINCT FROM true
GROUP BY c.id, period, CASE WHEN ae.amount < 0 THEN 'income' ELSE 'expense' END

View File

@@ -44,8 +44,9 @@ class IncomeStatement::FamilyStats
er.to_currency = :target_currency
)
WHERE a.family_id = :family_id
AND t.kind NOT IN ('funds_movement', 'one_time', 'cc_payment')
AND t.kind NOT IN ('funds_movement', 'one_time', 'cc_payment', 'investment_contribution')
AND ae.excluded = false
AND ae.exclude_from_cashflow = false
AND (t.extra -> 'simplefin' ->> 'pending')::boolean IS DISTINCT FROM true
AND (t.extra -> 'plaid' ->> 'pending')::boolean IS DISTINCT FROM true
GROUP BY period, CASE WHEN ae.amount < 0 THEN 'income' ELSE 'expense' END

View File

@@ -69,8 +69,9 @@ class IncomeStatement::Totals
er.from_currency = ae.currency AND
er.to_currency = :target_currency
)
WHERE at.kind NOT IN ('funds_movement', 'one_time', 'cc_payment')
WHERE at.kind NOT IN ('funds_movement', 'one_time', 'cc_payment', 'investment_contribution')
AND ae.excluded = false
AND ae.exclude_from_cashflow = false
AND a.family_id = :family_id
AND a.status IN ('draft', 'active')
GROUP BY c.id, c.parent_id, CASE WHEN ae.amount < 0 THEN 'income' ELSE 'expense' END;
@@ -95,8 +96,9 @@ class IncomeStatement::Totals
er.from_currency = ae.currency AND
er.to_currency = :target_currency
)
WHERE at.kind NOT IN ('funds_movement', 'one_time', 'cc_payment')
WHERE at.kind NOT IN ('funds_movement', 'one_time', 'cc_payment', 'investment_contribution')
AND ae.excluded = false
AND ae.exclude_from_cashflow = false
AND a.family_id = :family_id
AND a.status IN ('draft', 'active')
GROUP BY c.id, c.parent_id, CASE WHEN ae.amount < 0 THEN 'income' ELSE 'expense' END
@@ -126,6 +128,7 @@ class IncomeStatement::Totals
WHERE a.family_id = :family_id
AND a.status IN ('draft', 'active')
AND ae.excluded = false
AND ae.exclude_from_cashflow = false
AND ae.date BETWEEN :start_date AND :end_date
GROUP BY c.id, c.parent_id, CASE WHEN ae.amount < 0 THEN 'income' ELSE 'expense' END, CASE WHEN t.category_id IS NULL THEN true ELSE false END
SQL