fix(wise): address PR #2433 review feedback

- Wire @wise_items into AccountsController#index so linked accounts appear on /accounts
- Fix find_wise_account_for_linking to preserve .active scope via .merge instead of .then
- Fix cross-currency incoming transfer amount to use targetValue instead of sourceValue
- Add missing select_profiles.session_expired locale key
- Add missing account_name interpolation to link_existing_account success notice
- Use i18n for profile type labels in profile_display_name
- Trigger wise_item.sync_later after account linking in all three linking actions
- Isolate fee transaction failure so it no longer aborts the main transfer import
- Use bare raise in WiseActivity/WiseEntry processors to preserve original backtrace
- Re-raise in WiseItem::Unlinking to prevent silently orphaned Holdings
- Fix avatar badge to use design system tokens (bg-container-inset, text-primary)
This commit is contained in:
xBlaz3kx
2026-06-23 21:09:14 +02:00
parent 41ae89d414
commit b68ceae70f
8 changed files with 45 additions and 18 deletions

View File

@@ -28,6 +28,7 @@ class AccountsController < ApplicationController
@sophtron_items = visible_provider_items(family.sophtron_items.ordered.includes(:syncs, :sophtron_accounts))
@binance_items = visible_provider_items(family.binance_items.ordered.includes(:binance_accounts, :accounts, :syncs))
@questrade_items = visible_provider_items(family.questrade_items.ordered.includes(:syncs, questrade_accounts: :account_provider))
@wise_items = visible_provider_items(family.wise_items.ordered.includes(:syncs, :wise_accounts))
# Build sync stats maps for all providers
build_sync_stats_maps
@@ -451,5 +452,12 @@ class AccountsController < ApplicationController
linked: linked, unlinked: accounts.size - linked, total: accounts.size
}
end
# Wise sync stats
@wise_sync_stats_map = {}
@wise_items.each do |item|
latest_sync = item.syncs.ordered.first
@wise_sync_stats_map[item.id] = latest_sync&.sync_stats || {}
end
end
end

View File

@@ -145,6 +145,8 @@ class WiseItemsController < ApplicationController
provider: wise_account
)
@wise_item.sync_later unless @wise_item.syncing?
redirect_to accounts_path, notice: t(".success")
rescue => e
Rails.logger.error "WiseItemsController#complete_account_setup - #{e.class}: #{e.message}"
@@ -176,6 +178,7 @@ class WiseItemsController < ApplicationController
account = Account.create_from_wise_account(wise_account)
AccountProvider.create!(account: account, provider: wise_account)
wise_account.wise_item.sync_later unless wise_account.wise_item.syncing?
redirect_to safe_return_to_path || accounts_path, notice: t("wise_items.link_accounts.success")
rescue => e
@@ -206,8 +209,9 @@ class WiseItemsController < ApplicationController
end
AccountProvider.create!(account: account, provider: wise_account)
wise_account.wise_item.sync_later unless wise_account.wise_item.syncing?
redirect_to safe_return_to_path || accounts_path, notice: t("wise_items.link_existing_account.success")
redirect_to safe_return_to_path || accounts_path, notice: t("wise_items.link_existing_account.success", account_name: account.name)
rescue => e
Rails.logger.error "WiseItemsController#link_existing_account - #{e.class}: #{e.message}"
redirect_to accounts_path, alert: t("wise_items.link_existing_account.failed")
@@ -243,19 +247,19 @@ class WiseItemsController < ApplicationController
def find_wise_account_for_linking(wise_account_id)
return nil if wise_account_id.blank?
Current.family.wise_items.active
.joins(:wise_accounts)
.then { |_| WiseAccount.joins(:wise_item).where(wise_items: { family_id: Current.family.id }, id: wise_account_id) }
.first
WiseAccount.joins(:wise_item)
.merge(Current.family.wise_items.active)
.find_by(id: wise_account_id)
end
def profile_display_name(profile)
type = profile["type"] == "business" ? "Business" : "Personal"
type_key = profile["type"] == "business" ? :business : :personal
type_label = I18n.t("wise_items.profile_types.#{type_key}")
details = profile["details"] || {}
name = details["name"].presence ||
[ details["firstName"], details["lastName"] ].compact.join(" ").presence
name.present? ? "#{name} (#{type})" : "Wise #{type}"
name.present? ? "#{name} (#{type_label})" : "Wise #{type_label}"
end
def render_provider_panel_success(message)

View File

@@ -28,7 +28,7 @@ class WiseActivity::Processor
raise
rescue => e
Rails.logger.error "WiseActivity::Processor - Error for activity #{safe_id}: #{e.class} - #{e.message}"
raise StandardError, e.message
raise
end
private

View File

@@ -25,17 +25,22 @@ class WiseEntry::Processor
end
result = import_main_transaction
import_fee_transaction if fee > 0
if fee > 0
begin
import_fee_transaction
rescue StandardError => e
Rails.logger.warn "WiseEntry::Processor - Fee transaction failed for transfer #{safe_id}: #{e.message}"
end
end
result
rescue ArgumentError => e
Rails.logger.error "WiseEntry::Processor - Validation error for transfer #{safe_id}: #{e.message}"
raise
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
Rails.logger.error "WiseEntry::Processor - Failed to save transfer #{safe_id}: #{e.message}"
raise StandardError, "Failed to import transaction: #{e.message}"
rescue => e
Rails.logger.error "WiseEntry::Processor - Unexpected error for transfer #{safe_id}: #{e.class} - #{e.message}"
raise StandardError, "Unexpected error: #{e.message}"
raise
end
private
@@ -93,9 +98,13 @@ class WiseEntry::Processor
# Expenses (outgoing) → positive amount in Sure convention.
# Incomes (incoming) → negative amount in Sure convention.
# Incoming cross-currency transfers use targetValue (what actually arrived) not sourceValue.
def main_amount
value = data[:sourceValue].to_d.abs
outgoing? ? value : -value
if outgoing?
data[:sourceValue].to_d.abs
else
-(data[:targetValue].to_d.abs)
end
end
# Fee is the difference between what was debited and what the recipient received,

View File

@@ -31,6 +31,7 @@ module WiseItem::Unlinking
"WiseItem Unlinker: failed to fully unlink provider account ##{provider_account.id} (links=#{link_ids.inspect}): #{e.class} - #{e.message}"
)
result[:error] = e.message
raise
end
end

View File

@@ -17,7 +17,7 @@
) %>
<% end %>
<% if @manual_accounts.empty? && @plaid_items.empty? && @simplefin_items.empty? && @lunchflow_items.empty? && @akahu_items.empty? && @up_items.empty? && @enable_banking_items.empty? && @coinstats_items.empty? && @coinbase_items.empty? && @mercury_items.empty? && @brex_items.empty? && @ibkr_items.empty? && @snaptrade_items.empty? && @indexa_capital_items.empty? && @sophtron_items.empty? && @binance_items.empty? && @questrade_items.empty? %>
<% if @manual_accounts.empty? && @plaid_items.empty? && @simplefin_items.empty? && @lunchflow_items.empty? && @akahu_items.empty? && @up_items.empty? && @enable_banking_items.empty? && @coinstats_items.empty? && @coinbase_items.empty? && @mercury_items.empty? && @brex_items.empty? && @ibkr_items.empty? && @snaptrade_items.empty? && @indexa_capital_items.empty? && @sophtron_items.empty? && @binance_items.empty? && @questrade_items.empty? && @wise_items.empty? %>
<%= render "empty" %>
<% else %>
<div class="space-y-2">
@@ -69,6 +69,10 @@
<%= render @binance_items.sort_by(&:created_at) %>
<% end %>
<% if @wise_items.any? %>
<%= render @wise_items.sort_by(&:created_at) %>
<% end %>
<% if @snaptrade_items.any? %>
<%= render @snaptrade_items.sort_by(&:created_at) %>
<% end %>

View File

@@ -7,8 +7,8 @@
<div class="flex items-center gap-2">
<%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %>
<div class="flex items-center justify-center h-8 w-8 bg-green-500 rounded-full">
<p class="text-white text-xs font-bold">WI</p>
<div class="flex items-center justify-center h-8 w-8 bg-container-inset rounded-full">
<p class="text-primary text-xs font-bold">WI</p>
</div>
<div class="pl-1 text-sm">

View File

@@ -93,6 +93,7 @@ en:
one: "Successfully connected %{count} Wise profile"
other: "Successfully connected %{count} Wise profiles"
select_profiles:
session_expired: Session expired. Please try connecting again.
title: Select Wise Profiles
subtitle: Choose which profiles to connect
description: Your Wise account has multiple profiles. Select the ones you want to sync with Sure.