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

@@ -338,7 +338,7 @@ class ReportsController < ApplicationController
.joins(:entry)
.joins(entry: :account)
.where(accounts: { family_id: Current.family.id, status: [ "draft", "active" ] })
.where(entries: { entryable_type: "Transaction", excluded: false, date: @period.date_range })
.where(entries: { entryable_type: "Transaction", excluded: false, exclude_from_cashflow: false, date: @period.date_range })
.where.not(kind: [ "funds_movement", "one_time", "cc_payment" ])
.includes(entry: :account, category: [])
@@ -350,7 +350,7 @@ class ReportsController < ApplicationController
.joins(:entry)
.joins(entry: :account)
.where(accounts: { family_id: Current.family.id, status: [ "draft", "active" ] })
.where(entries: { entryable_type: "Trade", excluded: false, date: @period.date_range })
.where(entries: { entryable_type: "Trade", excluded: false, exclude_from_cashflow: false, date: @period.date_range })
.includes(entry: :account, category: [])
# Get sort parameters
@@ -519,7 +519,7 @@ class ReportsController < ApplicationController
.joins(:entry)
.joins(entry: :account)
.where(accounts: { family_id: Current.family.id, status: [ "draft", "active" ] })
.where(entries: { entryable_type: "Transaction", excluded: false, date: @period.date_range })
.where(entries: { entryable_type: "Transaction", excluded: false, exclude_from_cashflow: false, date: @period.date_range })
.where.not(kind: [ "funds_movement", "one_time", "cc_payment" ])
.includes(entry: :account, category: [])
@@ -556,7 +556,7 @@ class ReportsController < ApplicationController
.joins(:entry)
.joins(entry: :account)
.where(accounts: { family_id: Current.family.id, status: [ "draft", "active" ] })
.where(entries: { entryable_type: "Transaction", excluded: false, date: @period.date_range })
.where(entries: { entryable_type: "Transaction", excluded: false, exclude_from_cashflow: false, date: @period.date_range })
.where.not(kind: [ "funds_movement", "one_time", "cc_payment" ])
.includes(entry: :account, category: [])
@@ -567,7 +567,7 @@ class ReportsController < ApplicationController
.joins(:entry)
.joins(entry: :account)
.where(accounts: { family_id: Current.family.id, status: [ "draft", "active" ] })
.where(entries: { entryable_type: "Trade", excluded: false, date: @period.date_range })
.where(entries: { entryable_type: "Trade", excluded: false, exclude_from_cashflow: false, date: @period.date_range })
.includes(entry: :account, category: [])
# Group by category, type, and month

View File

@@ -217,8 +217,8 @@ class TransactionsController < ApplicationController
def entry_params
entry_params = params.require(:entry).permit(
:name, :date, :amount, :currency, :excluded, :notes, :nature, :entryable_type,
entryable_attributes: [ :id, :category_id, :merchant_id, :kind, { tag_ids: [] } ]
:name, :date, :amount, :currency, :excluded, :exclude_from_cashflow, :notes, :nature, :entryable_type,
entryable_attributes: [ :id, :category_id, :merchant_id, :kind, :investment_activity_label, { tag_ids: [] } ]
)
nature = entry_params.delete(:nature)