mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-05 13:35:21 +02:00
Scaffold out Account Syncing (#474)
* Add trends, time series, seed data * Remove test data * Replace old view values with helpers * Fix tooltip bugs in D3 chart * Fix tests * Fix smoke test * Add CRUD actions for valuations * Scaffold out inline editing with Turbo * Refactor series logic * Scaffold out basic sync process for accounts * Fix tests
This commit is contained in:
parent
b5b2d335fd
commit
7e324f1b53
25 changed files with 328 additions and 185 deletions
|
@ -6,16 +6,32 @@ class Account < ApplicationRecord
|
|||
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
|
||||
|
||||
delegate :type_name, to: :accountable
|
||||
|
||||
before_create :check_currency
|
||||
|
||||
# Show all valuations in history table (no date range filtering)
|
||||
def valuations_with_trend
|
||||
series_for(valuations, :value)
|
||||
def balance_series(period)
|
||||
filtered_balances = balances.in_period(period).order(:date)
|
||||
return nil if filtered_balances.empty?
|
||||
|
||||
series_data = [ nil, *filtered_balances ].each_cons(2).map do |previous, current|
|
||||
trend = current&.trend(previous)
|
||||
{ data: current, trend: { amount: trend&.amount, direction: trend&.direction, percent: trend&.percent } }
|
||||
end
|
||||
|
||||
last_balance = series_data.last[:data]
|
||||
|
||||
{
|
||||
series_data: series_data,
|
||||
last_balance: last_balance.balance,
|
||||
trend: last_balance.trend(series_data.first[:data])
|
||||
}
|
||||
end
|
||||
|
||||
def balances_with_trend(date_range = default_date_range)
|
||||
series_for(balances, :balance, date_range)
|
||||
def valuation_series
|
||||
series_data = [ nil, *valuations.order(:date) ].each_cons(2).map do |previous, current|
|
||||
{ value: current, trend: current&.trend(previous) }
|
||||
end
|
||||
|
||||
series_data.reverse_each
|
||||
end
|
||||
|
||||
def check_currency
|
||||
|
@ -27,36 +43,4 @@ class Account < ApplicationRecord
|
|||
self.converted_currency = self.family.currency
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_date_range
|
||||
{ start: 30.days.ago.to_date, end: Date.today }
|
||||
end
|
||||
|
||||
# TODO: probably a better abstraction for this in the future
|
||||
def series_for(collection, value_attr, date_range = {})
|
||||
collection = filtered_by_date_for(collection, date_range)
|
||||
overall_trend = Trend.new(collection.last&.send(value_attr), collection.first&.send(value_attr))
|
||||
|
||||
collection_with_trends = [ nil, *collection ].each_cons(2).map do |previous, current|
|
||||
{
|
||||
current: current,
|
||||
previous: previous,
|
||||
date: current.date,
|
||||
currency: current.currency,
|
||||
value: current.send(value_attr),
|
||||
trend: Trend.new(current.send(value_attr), previous&.send(value_attr))
|
||||
}
|
||||
end
|
||||
|
||||
{ date_range: date_range, trend: overall_trend, series: collection_with_trends }
|
||||
end
|
||||
|
||||
def filtered_by_date_for(association, date_range)
|
||||
scope = association
|
||||
scope = scope.where("date >= ?", date_range[:start]) if date_range[:start]
|
||||
scope = scope.where("date <= ?", date_range[:end]) if date_range[:end]
|
||||
scope.order(:date).to_a
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
class AccountBalance < ApplicationRecord
|
||||
belongs_to :account
|
||||
|
||||
scope :in_period, ->(period) { period.date_range.nil? ? all : where(date: period.date_range) }
|
||||
|
||||
def trend(previous)
|
||||
Trend.new(balance, previous&.balance)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
# Used for manual account value adjustments (e.g. to correct for a missing transaction)
|
||||
class Adjustment < Valuation
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
# Used to update the value of an account based on a manual or external appraisal (i.e. Zillow)
|
||||
class Appraisal < Valuation
|
||||
end
|
25
app/models/period.rb
Normal file
25
app/models/period.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class Period
|
||||
attr_reader :name, :date_range
|
||||
|
||||
def self.find_by_name(name)
|
||||
INDEX[name]
|
||||
end
|
||||
|
||||
def self.names
|
||||
INDEX.keys.sort
|
||||
end
|
||||
|
||||
def initialize(name:, date_range:)
|
||||
@name = name
|
||||
@date_range = date_range
|
||||
end
|
||||
|
||||
BUILTIN = [
|
||||
new(name: "all", date_range: nil),
|
||||
new(name: "last_7_days", date_range: 7.days.ago.to_date..Date.current),
|
||||
new(name: "last_30_days", date_range: 30.days.ago.to_date..Date.current),
|
||||
new(name: "last_365_days", date_range: 365.days.ago.to_date..Date.current)
|
||||
]
|
||||
|
||||
INDEX = BUILTIN.index_by(&:name)
|
||||
end
|
|
@ -1,5 +1,20 @@
|
|||
# STI model to represent a point-in-time "valuation" of an account's value
|
||||
# Types include: Appraisal, Adjustment
|
||||
class Valuation < ApplicationRecord
|
||||
belongs_to :account
|
||||
|
||||
after_commit :sync_account_balances, on: [ :create, :update ]
|
||||
after_destroy :sync_account_balances_after_destroy
|
||||
|
||||
def trend(previous)
|
||||
Trend.new(value, previous&.value)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sync_account_balances_after_destroy
|
||||
AccountBalanceSyncJob.perform_later(account_id: account_id, valuation_date: date, sync_type: "valuation", sync_action: "destroy")
|
||||
end
|
||||
|
||||
def sync_account_balances
|
||||
AccountBalanceSyncJob.perform_later(account_id: account_id, valuation_date: date, sync_type: "valuation", sync_action: "update")
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue