Localize investment activity labels and improve transaction processing

- Replaced hardcoded activity labels with `I18n` translations for better localization.
- Updated `transactions` views to display localized labels dynamically.
- Fixed `InvestmentActivityDetector` to enhance dividend detection.
- Refined `Account::ProviderImportAdapter` to prevent unnecessary updates and ensure transactional consistency.
- Improved error handling and feedback in rake tasks for invalid arguments.
This commit is contained in:
Josh Waldrep
2026-01-12 09:19:09 -05:00
parent e5fbdfb593
commit 307a8bb760
6 changed files with 30 additions and 8 deletions

View File

@@ -119,11 +119,12 @@ class Account::ProviderImportAdapter
# Set investment activity label if provided and not already set
if investment_activity_label.present? && entry.entryable.is_a?(Transaction)
if entry.transaction.investment_activity_label.blank?
entry.transaction.update!(investment_activity_label: investment_activity_label)
entry.transaction.assign_attributes(investment_activity_label: investment_activity_label)
end
end
entry.save!
entry.transaction.save! if entry.transaction.changed?
# AFTER save: For NEW posted transactions, check for fuzzy matches to SUGGEST (not auto-claim)
# This handles tip adjustments where auto-matching is too risky

View File

@@ -119,8 +119,9 @@ class InvestmentActivityDetector
end
# Check for dividend patterns
if description == "CASH" || description.include?("DIVIDEND") ||
description.include?("DISTRIBUTION")
# "CASH" alone typically indicates dividend payout in brokerage feeds (only for inflows)
if description.include?("DIVIDEND") || description.include?("DISTRIBUTION") ||
(description == "CASH" && amount < 0)
return "Dividend"
end

View File

@@ -87,7 +87,7 @@
<%# Investment activity label badge %>
<% if transaction.investment_activity_label.present? %>
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-alpha-black-50 text-secondary" title="<%= t("transactions.transaction.activity_type_tooltip") %>">
<%= transaction.investment_activity_label %>
<%= t("transactions.activity_labels.#{transaction.investment_activity_label.parameterize(separator: '_')}") %>
</span>
<% end %>

View File

@@ -240,7 +240,7 @@
<%= ef.select :investment_activity_label,
options_for_select(
[["—", nil]] + Transaction::ACTIVITY_LABELS.map { |l| [l, l] },
[["—", nil]] + Transaction::ACTIVITY_LABELS.map { |l| [t("transactions.activity_labels.#{l.parameterize(separator: '_')}"), l] },
@entry.entryable.investment_activity_label
),
{ label: false },
@@ -260,8 +260,8 @@
<%= f.fields_for :entryable do |ef| %>
<div class="flex cursor-pointer items-center gap-4 justify-between">
<div class="text-sm space-y-1">
<h4 class="text-primary">One-time <%= @entry.amount.negative? ? "Income" : "Expense" %></h4>
<p class="text-secondary">One-time transactions will be excluded from certain budgeting calculations and reports to help you see what's really important.</p>
<h4 class="text-primary"><%= t(".one_time_title", type: @entry.amount.negative? ? t("transactions.form.income") : t("transactions.form.expense")) %></h4>
<p class="text-secondary"><%= t(".one_time_description") %></p>
</div>
<%= ef.toggle :kind, {

View File

@@ -38,6 +38,22 @@ en:
exclude_from_cashflow_description_investment: Hide from income/expense reports and Sankey chart. Use for internal investment activity like fund swaps, reinvestments, or money market sweeps.
activity_type: Activity Type
activity_type_description: Type of investment activity (Buy, Sell, Dividend, etc.). Auto-detected or set manually.
one_time_title: One-time %{type}
one_time_description: One-time transactions will be excluded from certain budgeting calculations and reports to help you see what's really important.
activity_labels:
buy: Buy
sell: Sell
sweep_in: Sweep In
sweep_out: Sweep Out
dividend: Dividend
reinvestment: Reinvestment
interest: Interest
fee: Fee
transfer: Transfer
contribution: Contribution
withdrawal: Withdrawal
exchange: Exchange
other: Other
mark_recurring: Mark as Recurring
mark_recurring_subtitle: Track this as a recurring transaction. Amount variance is automatically calculated from past 6 months of similar transactions.
mark_recurring_title: Recurring Transaction

View File

@@ -78,6 +78,7 @@ namespace :sure do
# Find transactions (optionally include already-labeled if force=true)
entries = account.entries
.joins("INNER JOIN transactions ON transactions.id = entries.entryable_id AND entries.entryable_type = 'Transaction'")
.includes(:entryable)
unless force
entries = entries.where("transactions.investment_activity_label IS NULL OR transactions.investment_activity_label = ''")
@@ -160,8 +161,11 @@ namespace :sure do
true
elsif %w[1 true yes y].include?(dry_raw)
true
else
elsif %w[0 false no n].include?(dry_raw)
false
else
puts({ ok: false, error: "invalid_argument", message: "dry_run must be one of: true/yes/1 or false/no/0" }.to_json)
exit 1
end
account = Account.find(account_id)