mirror of
https://github.com/we-promise/sure.git
synced 2026-05-24 13:04:56 +00:00
* Display multi-currency holdings correctly * Implement IBKR provider * Fix: Use historical exchange rate for historical prices * Add brokerage exchange rate for trades * Sync historical balances from IBKR * Add logos in activity history * Fix privacy mode blur in account view * Improve IBKR XML Flex report parser errors
237 lines
7.8 KiB
Ruby
237 lines
7.8 KiB
Ruby
class IbkrItemsController < ApplicationController
|
|
before_action :set_ibkr_item, only: [ :update, :destroy, :sync, :setup_accounts, :complete_account_setup ]
|
|
before_action :require_admin!, only: [ :create, :select_accounts, :select_existing_account, :link_existing_account, :update, :destroy, :sync, :setup_accounts, :complete_account_setup ]
|
|
|
|
def create
|
|
@ibkr_item = Current.family.ibkr_items.build(ibkr_item_params)
|
|
@ibkr_item.name ||= t("ibkr_items.defaults.name")
|
|
|
|
if @ibkr_item.save
|
|
@ibkr_item.sync_later
|
|
|
|
if turbo_frame_request?
|
|
flash.now[:notice] = t(".success")
|
|
render turbo_stream: [
|
|
turbo_stream.replace(
|
|
"ibkr-providers-panel",
|
|
partial: "settings/providers/ibkr_panel"
|
|
),
|
|
*flash_notification_stream_items
|
|
]
|
|
else
|
|
redirect_to accounts_path, notice: t(".success"), status: :see_other
|
|
end
|
|
else
|
|
@error_message = @ibkr_item.errors.full_messages.join(", ")
|
|
|
|
if turbo_frame_request?
|
|
render turbo_stream: turbo_stream.replace(
|
|
"ibkr-providers-panel",
|
|
partial: "settings/providers/ibkr_panel",
|
|
locals: { error_message: @error_message }
|
|
), status: :unprocessable_entity
|
|
else
|
|
redirect_to settings_providers_path, alert: @error_message, status: :see_other
|
|
end
|
|
end
|
|
end
|
|
|
|
def update
|
|
attrs = ibkr_item_params.to_h
|
|
attrs["query_id"] = @ibkr_item.query_id if attrs["query_id"].blank?
|
|
attrs["token"] = @ibkr_item.token if attrs["token"].blank?
|
|
|
|
if @ibkr_item.update(attrs.merge(status: :good))
|
|
@ibkr_item.sync_later unless @ibkr_item.syncing?
|
|
|
|
if turbo_frame_request?
|
|
flash.now[:notice] = t(".success")
|
|
render turbo_stream: [
|
|
turbo_stream.replace(
|
|
"ibkr-providers-panel",
|
|
partial: "settings/providers/ibkr_panel"
|
|
),
|
|
*flash_notification_stream_items
|
|
]
|
|
else
|
|
redirect_to accounts_path, notice: t(".success"), status: :see_other
|
|
end
|
|
else
|
|
@error_message = @ibkr_item.errors.full_messages.join(", ")
|
|
|
|
if turbo_frame_request?
|
|
render turbo_stream: turbo_stream.replace(
|
|
"ibkr-providers-panel",
|
|
partial: "settings/providers/ibkr_panel",
|
|
locals: { error_message: @error_message }
|
|
), status: :unprocessable_entity
|
|
else
|
|
redirect_to settings_providers_path, alert: @error_message, status: :see_other
|
|
end
|
|
end
|
|
end
|
|
|
|
def destroy
|
|
begin
|
|
@ibkr_item.unlink_all!(dry_run: false)
|
|
rescue => e
|
|
Rails.logger.warn("IBKR unlink during destroy failed: #{e.class} - #{e.message}")
|
|
end
|
|
|
|
@ibkr_item.destroy_later
|
|
redirect_to settings_providers_path, notice: t(".success"), status: :see_other
|
|
end
|
|
|
|
def sync
|
|
@ibkr_item.sync_later unless @ibkr_item.syncing?
|
|
|
|
respond_to do |format|
|
|
format.html { redirect_back_or_to accounts_path }
|
|
format.json { head :ok }
|
|
end
|
|
end
|
|
|
|
def select_accounts
|
|
ibkr_item = current_ibkr_item
|
|
unless ibkr_item
|
|
redirect_to settings_providers_path, alert: t(".not_configured")
|
|
return
|
|
end
|
|
|
|
redirect_to setup_accounts_ibkr_item_path(ibkr_item)
|
|
end
|
|
|
|
def select_existing_account
|
|
@account = Current.family.accounts.find(params[:account_id])
|
|
@available_ibkr_accounts = Current.family.ibkr_items
|
|
.includes(ibkr_accounts: { account_provider: :account })
|
|
.flat_map(&:ibkr_accounts)
|
|
.select { |ibkr_account| ibkr_account.account_provider.nil? }
|
|
.sort_by { |ibkr_account| ibkr_account.updated_at || ibkr_account.created_at }
|
|
.reverse
|
|
|
|
render :select_existing_account, layout: false
|
|
end
|
|
|
|
def link_existing_account
|
|
account = Current.family.accounts.find_by(id: params[:account_id])
|
|
ibkr_account = Current.family.ibkr_items
|
|
.joins(:ibkr_accounts)
|
|
.where(ibkr_accounts: { id: params[:ibkr_account_id] })
|
|
.first
|
|
&.ibkr_accounts
|
|
&.find_by(id: params[:ibkr_account_id])
|
|
|
|
if account.blank? || ibkr_account.blank?
|
|
redirect_to settings_providers_path, alert: t(".not_found")
|
|
return
|
|
end
|
|
|
|
if account.accountable_type != "Investment" || account.account_providers.any? || account.plaid_account_id.present? || account.simplefin_account_id.present?
|
|
redirect_to account_path(account), alert: t(".only_manual_investment")
|
|
return
|
|
end
|
|
|
|
provider = nil
|
|
|
|
ibkr_account.with_lock do
|
|
if ibkr_account.current_account.present?
|
|
redirect_to account_path(account), alert: t(".already_linked")
|
|
return
|
|
end
|
|
|
|
provider = ibkr_account.ensure_account_provider!(account)
|
|
end
|
|
|
|
raise "Failed to create AccountProvider link" unless provider
|
|
|
|
begin
|
|
IbkrAccount::Processor.new(ibkr_account.reload).process
|
|
rescue => e
|
|
Rails.logger.error("Failed to process linked IBKR account #{ibkr_account.id}: #{e.class} - #{e.message}")
|
|
end
|
|
|
|
ibkr_account.ibkr_item.sync_later unless ibkr_account.ibkr_item.syncing?
|
|
redirect_to account_path(account), notice: t(".success"), status: :see_other
|
|
rescue => e
|
|
Rails.logger.error("Failed to link existing IBKR account: #{e.class} - #{e.message}")
|
|
redirect_to settings_providers_path, alert: t(".failed"), status: :see_other
|
|
end
|
|
|
|
def setup_accounts
|
|
@ibkr_accounts = @ibkr_item.ibkr_accounts.includes(account_provider: :account)
|
|
@linked_accounts = @ibkr_accounts.select { |ibkr_account| ibkr_account.current_account.present? }
|
|
@unlinked_accounts = @ibkr_accounts.reject { |ibkr_account| ibkr_account.current_account.present? }
|
|
|
|
no_accounts = @linked_accounts.blank? && @unlinked_accounts.blank?
|
|
latest_sync = @ibkr_item.syncs.ordered.first
|
|
should_sync = latest_sync.nil? || !latest_sync.completed?
|
|
|
|
if no_accounts && !@ibkr_item.syncing? && should_sync
|
|
@ibkr_item.sync_later
|
|
end
|
|
|
|
@linkable_accounts = Current.family.accounts
|
|
.visible
|
|
.where(accountable_type: "Investment")
|
|
.left_joins(:account_providers)
|
|
.where(account_providers: { id: nil })
|
|
.order(:name)
|
|
|
|
@syncing = @ibkr_item.syncing?
|
|
@waiting_for_sync = no_accounts && @syncing
|
|
@no_accounts_found = no_accounts && !@syncing && @ibkr_item.last_synced_at.present?
|
|
end
|
|
|
|
def complete_account_setup
|
|
selected_accounts = Array(params[:account_ids]).reject(&:blank?)
|
|
created_accounts = []
|
|
|
|
selected_accounts.each do |ibkr_account_id|
|
|
ibkr_account = @ibkr_item.ibkr_accounts.find_by(id: ibkr_account_id)
|
|
next unless ibkr_account
|
|
|
|
ibkr_account.with_lock do
|
|
next if ibkr_account.current_account.present?
|
|
|
|
account = Account.create_from_ibkr_account(ibkr_account)
|
|
ibkr_account.ensure_account_provider!(account)
|
|
created_accounts << account
|
|
end
|
|
|
|
begin
|
|
IbkrAccount::Processor.new(ibkr_account.reload).process
|
|
rescue => e
|
|
Rails.logger.error("Failed to process IBKR account #{ibkr_account.id} after setup: #{e.class} - #{e.message}")
|
|
end
|
|
end
|
|
|
|
@ibkr_item.update!(pending_account_setup: @ibkr_item.unlinked_accounts_count.positive?)
|
|
@ibkr_item.sync_later if created_accounts.any?
|
|
|
|
if created_accounts.any?
|
|
redirect_to accounts_path, notice: t(".success", count: created_accounts.count), status: :see_other
|
|
elsif selected_accounts.empty?
|
|
redirect_to setup_accounts_ibkr_item_path(@ibkr_item), alert: t(".none_selected"), status: :see_other
|
|
else
|
|
redirect_to setup_accounts_ibkr_item_path(@ibkr_item), alert: t(".none_created"), status: :see_other
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def set_ibkr_item
|
|
@ibkr_item = Current.family.ibkr_items.find(params[:id])
|
|
end
|
|
|
|
def current_ibkr_item
|
|
active_items = Current.family.ibkr_items.active
|
|
|
|
active_items.syncable.ordered.first || active_items.ordered.first
|
|
end
|
|
|
|
def ibkr_item_params
|
|
params.require(:ibkr_item).permit(:name, :query_id, :token)
|
|
end
|
|
end
|