diff --git a/app/controllers/account/holdings_controller.rb b/app/controllers/account/holdings_controller.rb index bfda09fb..9ded4165 100644 --- a/app/controllers/account/holdings_controller.rb +++ b/app/controllers/account/holdings_controller.rb @@ -9,9 +9,12 @@ class Account::HoldingsController < ApplicationController end def destroy - @holding.destroy_holding_and_entries! - - flash[:notice] = t(".success") + if @holding.account.plaid_account_id.present? + flash[:alert] = "You cannot delete this holding" + else + @holding.destroy_holding_and_entries! + flash[:notice] = t(".success") + end respond_to do |format| format.html { redirect_back_or_to account_path(@holding.account) } diff --git a/app/controllers/plaid_items_controller.rb b/app/controllers/plaid_items_controller.rb index 406da016..37efd5e3 100644 --- a/app/controllers/plaid_items_controller.rb +++ b/app/controllers/plaid_items_controller.rb @@ -22,7 +22,7 @@ class PlaidItemsController < ApplicationController end respond_to do |format| - format.html { redirect_to accounts_path } + format.html { redirect_back_or_to accounts_path } format.json { head :ok } end end diff --git a/app/models/account/syncer.rb b/app/models/account/syncer.rb index 8aeb0ba0..cd664e66 100644 --- a/app/models/account/syncer.rb +++ b/app/models/account/syncer.rb @@ -10,11 +10,11 @@ class Account::Syncer holdings = sync_holdings balances = sync_balances(holdings) account.reload - update_account_info(balances, holdings) unless account.plaid_account_id.present? + update_account_info(balances, holdings) unless plaid_sync? convert_records_to_family_currency(balances, holdings) unless account.currency == account.family.currency # Enrich if user opted in or if we're syncing transactions from a Plaid account on the hosted app - if account.family.data_enrichment_enabled? || (account.plaid_account_id.present? && Rails.application.config.app_mode.hosted?) + if account.family.data_enrichment_enabled? || (plaid_sync? && Rails.application.config.app_mode.hosted?) account.enrich_data else Rails.logger.info("Data enrichment is disabled, skipping enrichment for account #{account.id}") @@ -41,15 +41,13 @@ class Account::Syncer def sync_holdings calculator = Account::HoldingCalculator.new(account) - calculated_holdings = calculator.calculate(reverse: account.plaid_account_id.present?) + calculated_holdings = calculator.calculate(reverse: plaid_sync?) current_time = Time.now Account.transaction do load_holdings(calculated_holdings) - - # Purge outdated holdings - account.holdings.delete_by("date < ? OR security_id NOT IN (?)", account_start_date, calculated_holdings.map(&:security_id)) + purge_outdated_holdings unless plaid_sync? end calculated_holdings @@ -57,13 +55,11 @@ class Account::Syncer def sync_balances(holdings) calculator = Account::BalanceCalculator.new(account, holdings: holdings) - calculated_balances = calculator.calculate(reverse: account.plaid_account_id.present?, start_date: start_date) + calculated_balances = calculator.calculate(reverse: plaid_sync?, start_date: start_date) Account.transaction do load_balances(calculated_balances) - - # Purge outdated balances - account.balances.delete_by("date < ?", account_start_date) + purge_outdated_balances end calculated_balances @@ -131,4 +127,23 @@ class Account::Syncer unique_by: %i[account_id security_id date currency] ) end + + def purge_outdated_balances + account.balances.delete_by("date < ?", account_start_date) + end + + def plaid_sync? + account.plaid_account_id.present? + end + + def purge_outdated_holdings + portfolio_security_ids = account.entries.account_trades.map { |entry| entry.entryable.security_id }.uniq + + # If there are no securities in the portfolio, delete all holdings + if portfolio_security_ids.empty? + account.holdings.delete_all + else + account.holdings.delete_by("date < ? OR security_id NOT IN (?)", account_start_date, portfolio_security_ids) + end + end end diff --git a/app/views/account/holdings/index.html.erb b/app/views/account/holdings/index.html.erb index a4dfeb4d..6b80ab59 100644 --- a/app/views/account/holdings/index.html.erb +++ b/app/views/account/holdings/index.html.erb @@ -21,12 +21,12 @@
+ <%= render "account/holdings/cash", account: @account %> + + <%= render "account/holdings/ruler" %> + <% if @account.current_holdings.any? %> - <%= render "account/holdings/cash", account: @account %> - <%= render "account/holdings/ruler" %> <%= render partial: "account/holdings/holding", collection: @account.current_holdings, spacer_template: "ruler" %> - <% else %> -

<%= t(".no_holdings") %>

<% end %>
diff --git a/app/views/account/holdings/show.html.erb b/app/views/account/holdings/show.html.erb index 1dd09244..b783ac5b 100644 --- a/app/views/account/holdings/show.html.erb +++ b/app/views/account/holdings/show.html.erb @@ -87,26 +87,28 @@ -
- -

<%= t(".settings") %>

- <%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-secondary w-5" %> -
+ <% unless @holding.account.plaid_account_id.present? %> +
+ +

<%= t(".settings") %>

+ <%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-secondary w-5" %> +
-
-
-
-

<%= t(".delete_title") %>

-

<%= t(".delete_subtitle") %>

-
+
+
+
+

<%= t(".delete_title") %>

+

<%= t(".delete_subtitle") %>

+
- <%= button_to t(".delete"), + <%= button_to t(".delete"), account_holding_path(@holding), method: :delete, class: "rounded-lg px-3 py-2 text-red-500 text-sm font-medium border border-secondary", data: { turbo_confirm: true } %> +
-
-
+
+ <% end %> <% end %> diff --git a/test/models/account/syncer_test.rb b/test/models/account/syncer_test.rb index 261ab28c..5cc85ee9 100644 --- a/test/models/account/syncer_test.rb +++ b/test/models/account/syncer_test.rb @@ -17,6 +17,8 @@ class Account::SyncerTest < ActiveSupport::TestCase @account.family.update! currency: "USD" @account.update! currency: "EUR" + @account.entries.create!(date: 1.day.ago.to_date, currency: "EUR", amount: 500, name: "Buy AAPL", entryable: Account::Trade.new(security: securities(:aapl), qty: 10, price: 50, currency: "EUR")) + ExchangeRate.create!(date: 1.day.ago.to_date, from_currency: "EUR", to_currency: "USD", rate: 1.2) ExchangeRate.create!(date: Date.current, from_currency: "EUR", to_currency: "USD", rate: 2)