diff --git a/app/controllers/account/cashes_controller.rb b/app/controllers/account/cashes_controller.rb new file mode 100644 index 00000000..6afa3241 --- /dev/null +++ b/app/controllers/account/cashes_controller.rb @@ -0,0 +1,14 @@ +class Account::CashesController < ApplicationController + layout :with_sidebar + + before_action :set_account + + def index + end + + private + + def set_account + @account = Current.family.accounts.find(params[:account_id]) + end +end diff --git a/app/controllers/account/entries_controller.rb b/app/controllers/account/entries_controller.rb index 70c04e67..70de7c62 100644 --- a/app/controllers/account/entries_controller.rb +++ b/app/controllers/account/entries_controller.rb @@ -13,7 +13,7 @@ class Account::EntriesController < ApplicationController end def trades - @trades = @account.entries.account_trades.reverse_chronological + @trades = @account.entries.where(entryable_type: [ "Account::Transaction", "Account::Trade" ]).reverse_chronological end def new diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index a02e1c91..30a0e0d7 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -31,7 +31,8 @@ class AccountsController < ApplicationController end def show - @balance_series = @account.series(period: @period) + @series = @account.series(period: @period) + @trend = @series.trend end def edit diff --git a/app/helpers/account/cashes_helper.rb b/app/helpers/account/cashes_helper.rb new file mode 100644 index 00000000..ed8c2dfc --- /dev/null +++ b/app/helpers/account/cashes_helper.rb @@ -0,0 +1,13 @@ +module Account::CashesHelper + def brokerage_cash(account) + currency = Money::Currency.new(account.currency) + + account.holdings.build \ + date: Date.current, + qty: account.balance, + price: 1, + amount: account.balance, + currency: account.currency, + security: Security.new(ticker: currency.iso_code, name: currency.name) + end +end diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index 6817d7fb..6994578f 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -25,11 +25,12 @@ module AccountsHelper def account_tabs(account) holdings_tab = { key: "holdings", label: t("accounts.show.holdings"), path: account_path(account, tab: "holdings"), content_path: account_holdings_path(account) } + cash_tab = { key: "cash", label: t("accounts.show.cash"), path: account_path(account, tab: "cash"), content_path: account_cashes_path(account) } value_tab = { key: "valuations", label: t("accounts.show.value"), path: account_path(account, tab: "valuations"), content_path: valuation_account_entries_path(account) } transactions_tab = { key: "transactions", label: t("accounts.show.transactions"), path: account_path(account, tab: "transactions"), content_path: transaction_account_entries_path(account) } trades_tab = { key: "trades", label: t("accounts.show.trades"), path: account_path(account, tab: "trades"), content_path: trade_account_entries_path(account) } - return [ holdings_tab, trades_tab ] if account.investment? + return [ holdings_tab, cash_tab, trades_tab ] if account.investment? [ value_tab, transactions_tab ] end diff --git a/app/models/account.rb b/app/models/account.rb index 2ce6495f..e71390d6 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -28,8 +28,10 @@ class Account < ApplicationRecord delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy + delegate :value, :series, to: :accountable + class << self - def by_group(period: Period.all, currency: Money.default_currency) + def by_group(period: Period.all, currency: Money.default_currency.iso_code) grouped_accounts = { assets: ValueGroup.new("Assets", currency), liabilities: ValueGroup.new("Liabilities", currency) } Accountable.by_classification.each do |classification, types| @@ -82,18 +84,6 @@ class Account < ApplicationRecord classification == "asset" ? "up" : "down" end - def series(period: Period.all, currency: self.currency) - balance_series = balances.in_period(period).where(currency: Money::Currency.new(currency).iso_code) - - if balance_series.empty? && period.date_range.end == Date.current - TimeSeries.new([ { date: Date.current, value: balance_money.exchange_to(currency) } ]) - else - TimeSeries.from_collection(balance_series, :balance_money) - end - rescue Money::ConversionError - TimeSeries.new([]) - end - def update_balance!(balance) valuation = entries.account_valuations.find_by(date: Date.current) diff --git a/app/models/account/holding.rb b/app/models/account/holding.rb index 512caddb..73dd8c02 100644 --- a/app/models/account/holding.rb +++ b/app/models/account/holding.rb @@ -10,6 +10,8 @@ class Account::Holding < ApplicationRecord scope :chronological, -> { order(:date) } scope :current, -> { where(date: Date.current).order(amount: :desc) } + scope :in_period, ->(period) { period.date_range.nil? ? all : where(date: period.date_range) } + scope :known_value, -> { where.not(amount: nil) } scope :for, ->(security) { where(security_id: security).order(:date) } delegate :name, to: :security @@ -18,7 +20,7 @@ class Account::Holding < ApplicationRecord def weight return nil unless amount - portfolio_value = account.holdings.current.where.not(amount: nil).sum(&:amount) + portfolio_value = account.holdings.current.known_value.sum(&:amount) portfolio_value.zero? ? 1 : amount / portfolio_value * 100 end diff --git a/app/models/concerns/accountable.rb b/app/models/concerns/accountable.rb index 916e6cfd..9579929e 100644 --- a/app/models/concerns/accountable.rb +++ b/app/models/concerns/accountable.rb @@ -17,4 +17,20 @@ module Accountable included do has_one :account, as: :accountable, touch: true end + + def value + account.balance_money + end + + def series(period: Period.all, currency: account.currency) + balance_series = account.balances.in_period(period).where(currency: currency) + + if balance_series.empty? && period.date_range.end == Date.current + TimeSeries.new([ { date: Date.current, value: account.balance_money.exchange_to(currency) } ]) + else + TimeSeries.from_collection(balance_series, :balance_money) + end + rescue Money::ConversionError + TimeSeries.new([]) + end end diff --git a/app/models/investment.rb b/app/models/investment.rb index 145a617d..15ef92af 100644 --- a/app/models/investment.rb +++ b/app/models/investment.rb @@ -13,4 +13,35 @@ class Investment < ApplicationRecord [ "Roth 401k", "roth_401k" ], [ "Angel", "angel" ] ].freeze + + def value + account.balance_money + holdings_value + end + + def holdings_value + account.holdings.current.known_value.sum(&:amount) || Money.new(0, account.currency) + end + + def series(period: Period.all, currency: account.currency) + balance_series = account.balances.in_period(period).where(currency: currency) + holding_series = account.holdings.known_value.in_period(period).where(currency: currency) + + holdings_by_date = holding_series.group_by(&:date).transform_values do |holdings| + holdings.sum(&:amount) + end + + combined_series = balance_series.map do |balance| + holding_amount = holdings_by_date[balance.date] || 0 + + { date: balance.date, value: Money.new(balance.balance + holding_amount, currency) } + end + + if combined_series.empty? && period.date_range.end == Date.current + TimeSeries.new([ { date: Date.current, value: self.value.exchange_to(currency) } ]) + else + TimeSeries.new(combined_series) + end + rescue Money::ConversionError + TimeSeries.new([]) + end end diff --git a/app/models/provider/synth.rb b/app/models/provider/synth.rb index 05502d28..b9523051 100644 --- a/app/models/provider/synth.rb +++ b/app/models/provider/synth.rb @@ -24,6 +24,11 @@ class Provider::Synth prices: prices, success?: true, raw_response: prices.to_json + rescue StandardError => error + SecurityPriceResponse.new \ + success?: false, + error: error, + raw_response: error end def fetch_exchange_rate(from:, to:, date:) diff --git a/app/views/account/cashes/_cash.html.erb b/app/views/account/cashes/_cash.html.erb new file mode 100644 index 00000000..e5e2065d --- /dev/null +++ b/app/views/account/cashes/_cash.html.erb @@ -0,0 +1,21 @@ +<%# locals: (holding:) %> + +<%= turbo_frame_tag dom_id(holding) do %> +