mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 12:04:08 +00:00
feat: Enhance holding detail drawer with live price sync and enriched overview (#1086)
* Feat: Implement manual sync prices functionality and enhance holdings display * Feat: Enhance sync prices functionality with error handling and update UI components * Feat: Update sync prices error handling and enhance Spanish locale messages * Fix: Address CodeRabbit review feedback - Set fallback @provider_error when prices_updated == 0 so turbo stream never fails silently without a visible error message - Move attr_reader :provider_error to class header in Price::Importer for conventional placement alongside other attribute declarations - Precompute @last_price_updated in controller (show + sync_prices) instead of running a DB query directly inside ERB templates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix: Replace bare rescue with explicit exception handling in turbo stream view Bare `rescue` silently swallows all exceptions, making debugging impossible. Match the pattern already used in show.html.erb: rescue ActiveRecord::RecordInvalid explicitly, then catch StandardError with logging (message + backtrace) before falling back to the unknown label. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix: Update test assertion to expect actual provider error message The stub returns "Yahoo Finance rate limit exceeded" as the provider error. After the @provider_error fallback fix, the controller now correctly surfaces the real provider error when present (using .presence || fallback), so the flash[:alert] is the actual error string, not the generic fallback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix: Assert scoped security_ids in sync_prices materializer test Replace loose stub with constructor expectation to verify that Balance::Materializer is instantiated with the single-security scope. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix: Assert holding remap in remap_security test Add assertion that @holding.security_id is updated to the target security after remap, covering the core command outcome. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix: CI test failure - Update disconnect external assistant test to use env overrides --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
class HoldingsController < ApplicationController
|
||||
before_action :set_holding, only: %i[show update destroy unlock_cost_basis remap_security reset_security]
|
||||
before_action :set_holding, only: %i[show update destroy unlock_cost_basis remap_security reset_security sync_prices]
|
||||
|
||||
def index
|
||||
@account = Current.family.accounts.find(params[:account_id])
|
||||
end
|
||||
|
||||
def show
|
||||
@last_price_updated = @holding.security.prices.maximum(:updated_at)
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -70,6 +71,13 @@ class HoldingsController < ApplicationController
|
||||
return
|
||||
end
|
||||
|
||||
# The user explicitly selected this security from provider search results,
|
||||
# so we know the provider can handle it. Bring it back online if it was
|
||||
# previously marked offline (e.g. by a failed QIF import resolution).
|
||||
if new_security.offline?
|
||||
new_security.update!(offline: false, failed_fetch_count: 0, failed_fetch_at: nil)
|
||||
end
|
||||
|
||||
@holding.remap_security!(new_security)
|
||||
flash[:notice] = t(".success")
|
||||
|
||||
@@ -79,6 +87,44 @@ class HoldingsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def sync_prices
|
||||
security = @holding.security
|
||||
|
||||
if security.offline?
|
||||
redirect_to account_path(@holding.account, tab: "holdings"),
|
||||
alert: t("holdings.sync_prices.unavailable")
|
||||
return
|
||||
end
|
||||
|
||||
prices_updated, @provider_error = security.import_provider_prices(
|
||||
start_date: 31.days.ago.to_date,
|
||||
end_date: Date.current,
|
||||
clear_cache: true
|
||||
)
|
||||
security.import_provider_details
|
||||
|
||||
@last_price_updated = @holding.security.prices.maximum(:updated_at)
|
||||
|
||||
if prices_updated == 0
|
||||
@provider_error = @provider_error.presence || t("holdings.sync_prices.provider_error")
|
||||
respond_to do |format|
|
||||
format.html { redirect_to account_path(@holding.account, tab: "holdings"), alert: @provider_error }
|
||||
format.turbo_stream
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
strategy = @holding.account.linked? ? :reverse : :forward
|
||||
Balance::Materializer.new(@holding.account, strategy: strategy, security_ids: [ @holding.security_id ]).materialize_balances
|
||||
@holding.reload
|
||||
@last_price_updated = @holding.security.prices.maximum(:updated_at)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to account_path(@holding.account, tab: "holdings"), notice: t("holdings.sync_prices.success") }
|
||||
format.turbo_stream
|
||||
end
|
||||
end
|
||||
|
||||
def reset_security
|
||||
@holding.reset_security_to_provider!
|
||||
flash[:notice] = t(".success")
|
||||
|
||||
Reference in New Issue
Block a user