diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index e1c4d234..d2a834dc 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -10,6 +10,21 @@ class AccountsController < ApplicationController def show @account = Current.family.accounts.find(params[:id]) + + @period = Period.find_by_name(params[:period]) + if @period.nil? + start_date = params[:start_date].presence&.to_date + end_date = params[:end_date].presence&.to_date + if start_date.is_a?(Date) && end_date.is_a?(Date) && start_date <= end_date + @period = Period.new(name: "custom", date_range: start_date..end_date) + else + params[:period] = "last_30_days" + @period = Period.find_by_name(params[:period]) + end + end + + @balance_series = @account.balance_series(@period) + @valuation_series = @account.valuation_series end def create diff --git a/app/controllers/valuations_controller.rb b/app/controllers/valuations_controller.rb index 6e9bc9ef..e73c6287 100644 --- a/app/controllers/valuations_controller.rb +++ b/app/controllers/valuations_controller.rb @@ -4,8 +4,8 @@ class ValuationsController < ApplicationController def create @account = Current.family.accounts.find(params[:account_id]) - # TODO: handle STI once we allow for different types of valuations - @valuation = @account.valuations.new(valuation_params.merge(type: "Appraisal", currency: Current.family.currency)) + # TODO: placeholder logic until we have a better abstraction for trends + @valuation = @account.valuations.new(valuation_params.merge(currency: Current.family.currency)) if @valuation.save respond_to do |format| format.html { redirect_to account_path(@account), notice: "Valuation created" } @@ -41,11 +41,11 @@ class ValuationsController < ApplicationController def destroy @valuation = Valuation.find(params[:id]) - account = @valuation.account + @account = @valuation.account @valuation.destroy respond_to do |format| - format.html { redirect_to account_path(account), notice: "Valuation deleted" } + format.html { redirect_to account_path(@account), notice: "Valuation deleted" } format.turbo_stream end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e87992e7..15f075de 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -49,8 +49,8 @@ module ApplicationHelper end # Styles to use when displaying a change in value - def trend_styles(trend_direction) - bg_class, text_class, symbol, icon = case trend_direction + def trend_styles(trend) + bg_class, text_class, symbol, icon = case trend.direction when "up" [ "bg-green-500/5", "text-green-500", "+", "arrow-up" ] when "down" @@ -58,14 +58,19 @@ module ApplicationHelper when "flat" [ "bg-gray-500/5", "text-gray-500", "", "minus" ] else - raise ArgumentError, "Invalid trend direction: #{trend_direction}" + raise ArgumentError, "Invalid trend direction: #{trend.direction}" end { bg_class: bg_class, text_class: text_class, symbol: symbol, icon: icon } end - def trend_label(date_range) - start_date, end_date = date_range.values_at(:start, :end) + def trend_label(period) + return "since account creation" if period.date_range.nil? + start_date, end_date = period.date_range.first, period.date_range.last + + return "Starting from #{start_date.strftime('%b %d, %Y')}" if end_date.nil? + return "Ending at #{end_date.strftime('%b %d, %Y')}" if start_date.nil? + days_apart = (end_date - start_date).to_i case days_apart diff --git a/app/javascript/controllers/line_chart_controller.js b/app/javascript/controllers/line_chart_controller.js index e38a43b9..ffd7f099 100644 --- a/app/javascript/controllers/line_chart_controller.js +++ b/app/javascript/controllers/line_chart_controller.js @@ -36,30 +36,20 @@ export default class extends Controller { }[trendDirection]; } - /** - * @param {Array} balances - An array of objects where each object represents a balance entry. Each object should have the following properties: - * - date: {Date} The date of the balance entry. - * - value: {number} The numerical value of the balance. - * - formatted: {string} The formatted string representation of the balance value. - * - trend: {Object} An object containing information about the trend compared to the previous balance entry. It should have: - * - amount: {number} The numerical difference in value from the previous entry. - * - direction: {string} A string indicating the direction of the trend ("up", "down", or "flat"). - * - percent: {number} The percentage change from the previous entry. - */ drawChart(balances) { const data = balances.map((b) => ({ - ...b, - value: +b.value, - date: new Date(b.date), + date: new Date(b.data.date + "T00:00:00"), + value: +b.data.balance, styles: this.trendStyles(b.trend.direction), + trend: b.trend, formatted: { value: Intl.NumberFormat("en-US", { style: "currency", - currency: b.currency || "USD", - }).format(b.value), + currency: b.data.currency || "USD", + }).format(b.data.balance), change: Intl.NumberFormat("en-US", { style: "currency", - currency: b.currency || "USD", + currency: b.data.currency || "USD", signDisplay: "always", }).format(b.trend.amount), }, @@ -87,7 +77,7 @@ export default class extends Controller { ]) .attr("style", "max-width: 100%; height: auto; height: intrinsic;"); - const margin = { top: 20, right: 0, bottom: 30, left: 0 }, + const margin = { top: 20, right: 1, bottom: 30, left: 1 }, width = +svg.attr("width") - margin.left - margin.right, height = +svg.attr("height") - margin.top - margin.bottom, g = svg @@ -216,7 +206,7 @@ export default class extends Controller { .html( `
${d3.timeFormat("%b %Y")(d.date)}
+ }">${d3.timeFormat("%b %d, %Y")(d.date)}
?", valuation_date).order(:date).first&.date || Date.current) - 1.day + + balances_to_upsert = (update_period_start..update_period_end).map do |date| + { date: date, balance: updated_valuation.value, created_at: Time.current, updated_at: Time.current } + end + + account.balances.upsert_all(balances_to_upsert, unique_by: :index_account_balances_on_account_id_and_date) + + logger.info "Upserted balances for account #{account.id} from #{update_period_start} to #{update_period_end}" + end + + def handle_valuation_destroy(account:, valuation_date:) + prior_valuation = account.valuations.where("date < ?", valuation_date).order(:date).last + period_start = prior_valuation&.date + period_end = (account.valuations.where("date > ?", valuation_date).order(:date).first&.date || Date.current) - 1.day + + if prior_valuation + balances_to_upsert = (period_start..period_end).map do |date| + { date: date, balance: prior_valuation.value, created_at: Time.current, updated_at: Time.current } + end + + account.balances.upsert_all(balances_to_upsert, unique_by: :index_account_balances_on_account_id_and_date) + logger.info "Upserted balances for account #{account.id} from #{period_start} to #{period_end}" + else + delete_count = account.balances.where(date: period_start..period_end).delete_all + logger.info "Deleted #{delete_count} balances for account #{account.id} from #{period_start} to #{period_end}" + end + rescue => e + logger.error "Sync failed after valuation destroy operation on account #{account.id} with message: #{e.message}" + end +end diff --git a/app/models/account.rb b/app/models/account.rb index 1b0608bf..2b3eba50 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -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 diff --git a/app/models/account_balance.rb b/app/models/account_balance.rb index ded2bc15..5968c5d1 100644 --- a/app/models/account_balance.rb +++ b/app/models/account_balance.rb @@ -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 diff --git a/app/models/adjustment.rb b/app/models/adjustment.rb deleted file mode 100644 index 1cef0cca..00000000 --- a/app/models/adjustment.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Used for manual account value adjustments (e.g. to correct for a missing transaction) -class Adjustment < Valuation -end diff --git a/app/models/appraisal.rb b/app/models/appraisal.rb deleted file mode 100644 index ca58791a..00000000 --- a/app/models/appraisal.rb +++ /dev/null @@ -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 diff --git a/app/models/period.rb b/app/models/period.rb new file mode 100644 index 00000000..b4a81579 --- /dev/null +++ b/app/models/period.rb @@ -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 diff --git a/app/models/valuation.rb b/app/models/valuation.rb index 70dff5de..42d3dc88 100644 --- a/app/models/valuation.rb +++ b/app/models/valuation.rb @@ -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 diff --git a/app/views/accounts/_account_valuation_list.html.erb b/app/views/accounts/_account_valuation_list.html.erb new file mode 100644 index 00000000..dfe52615 --- /dev/null +++ b/app/views/accounts/_account_valuation_list.html.erb @@ -0,0 +1,48 @@ +<%# locals: (valuation_series:) %> +<% valuation_series.with_index do |valuation_item, index| %> + <% valuation, trend = valuation_item.values_at(:value, :trend) %> + <% valuation_styles = trend_styles(valuation_item[:trend]) %> + <%= turbo_frame_tag dom_id(valuation) do %> +
+
+
+ <%= lucide_icon(valuation_styles[:icon], class: "w-4 h-4 #{valuation_styles[:text_class]}") %> +
+
+
+
+

<%= valuation.date %>

+ <%# TODO: Add descriptive name of valuation %> +

Manually entered

+
+
<%= format_currency(valuation.value) %>
+
+
+ <% if trend.amount == 0 %> + No change + <% else %> + <%= valuation_styles[:symbol] %><%= format_currency(trend.amount.abs) %> + (<%= lucide_icon(valuation_styles[:icon], class: "w-4 h-4 align-text-bottom inline") %> <%= trend.percent %>%) + <% end %> +
+
+ + +
+
+ <% unless index == valuation_series.size - 1 %> +
+ <% end %> + <% end %> +<% end %> diff --git a/app/views/accounts/_sync_message.html.erb b/app/views/accounts/_sync_message.html.erb new file mode 100644 index 00000000..269c9518 --- /dev/null +++ b/app/views/accounts/_sync_message.html.erb @@ -0,0 +1,8 @@ +<%# locals: (is_syncing:) %> +<% if is_syncing %> +
+

+ Syncing your account balances. Please reload the page to see updated data. +

+
+<% end %> diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb index 25a82cfc..76ba4f56 100644 --- a/app/views/accounts/show.html.erb +++ b/app/views/accounts/show.html.erb @@ -1,5 +1,4 @@ -<% balances = @account.balances_with_trend %> -<% balance_styles = trend_styles(balances[:trend].direction) %> +<% balance_trend_styles = @balance_series.nil? ? {} : trend_styles(@balance_series[:trend]) %>
@@ -20,35 +19,43 @@
+ <%= turbo_frame_tag "sync_message" do %> + <%= render partial: "accounts/sync_message", locals: { is_syncing: @account.status == "SYNCING" } %> + <% end %>

Total Value

<%# TODO: Will need a better way to split a formatted monetary value into these 3 parts %>

- <%= number_to_currency(@account.converted_balance)[0] %> - <%= number_with_delimiter(@account.converted_balance.round) %> - .<%= number_to_currency(@account.converted_balance, precision: 2)[-2, 2] %> + <%= number_to_currency(@account.original_balance)[0] %> + <%= number_with_delimiter(@account.original_balance.round) %> + .<%= number_to_currency(@account.original_balance, precision: 2)[-2, 2] %>

- <% if balances[:trend].amount == 0 %> + <% if @balance_series.nil? %> +

Data not available for the selected period

+ <% elsif @balance_series[:trend].amount == 0 %>

No change vs. prior period

<% else %> -

- <%= balance_styles[:symbol] %><%= number_to_currency(balances[:trend].amount.abs, precision: 2) %> - (<%= lucide_icon(balances[:trend].amount > 0 ? 'arrow-up' : 'arrow-down', class: "w-4 h-4 align-text-bottom inline") %> <%= balances[:trend].percent %>%) - <%= trend_label(balances[:date_range]) %> +

+ <%= balance_trend_styles[:symbol] %><%= number_to_currency(@balance_series[:trend].amount.abs, precision: 2) %> + (<%= lucide_icon(@balance_series[:trend].amount > 0 ? 'arrow-up' : 'arrow-down', class: "w-4 h-4 align-text-bottom inline") %> <%= @balance_series[:trend].percent %>%) + <%= trend_label(@period) %>

<% end %>
-
-
- 1M - <%= lucide_icon("chevron-down", class: "w-5 h-5 text-gray-500") %> -
-
+ <%= form_with url: account_path(@account), method: :get, class: "flex items-center gap-4", html: { class: "" } do |f| %> + <%= f.select :period, options_for_select([['7D', 'last_7_days'], ['1M', 'last_30_days'], ["1Y", "last_365_days"], ['All', 'all']], selected: params[:period]), {}, { class: "block w-full border border-alpha-black-100 shadow-xs rounded-lg text-sm py-2 pr-8 pl-2 cursor-pointer", onchange: "this.form.submit();" } %> + <% end %>
-
+ <% if @balance_series %> +
+ <% else %> +
+

No data available for the selected period.

+
+ <% end %>
@@ -61,67 +68,22 @@
-
-
DATE
-
VALUE
-
CHANGE
-
+
+
date
+
+
+
value
+
+
change
+
-
- <% series = @account.valuations_with_trend[:series].reverse_each %> - <% series.with_index do |valuation, index| %> - <% valuation_styles = trend_styles(valuation[:trend].direction) %> - <%= turbo_frame_tag dom_id(valuation[:current]) do %> -
-
-
- <%= lucide_icon(valuation_styles[:icon], class: "w-4 h-4 #{valuation_styles[:text_class]}") %> -
-
-

<%= valuation[:date] %>

- <%# TODO: Add descriptive name of valuation %> -

Manually entered

-
-
-
<%= format_currency(valuation[:value]) %>
-
- <% if valuation[:trend].amount == 0 %> - No change - <% else %> - <%= valuation_styles[:symbol] %><%= format_currency(valuation[:trend].amount.abs) %> - (<%= lucide_icon(valuation_styles[:icon], class: "w-4 h-4 align-text-bottom inline") %> <%= valuation[:trend].percent %>%) - <% end %> -
-
- - -
-
-
- <% if index < series.size - 1 %> -
- <% end %> -
- <% end %> - <% end %> - <%= turbo_frame_tag dom_id(Valuation.new) do %> +
+ <%= turbo_frame_tag dom_id(Valuation.new) %> + <%= turbo_frame_tag "valuations_list" do %> + <%= render partial: "accounts/account_valuation_list", locals: { valuation_series: @valuation_series } %> <% end %>
- <%= link_to new_account_valuation_path(@account), data: { turbo_frame: "new_valuation" }, class: "hover:bg-white w-full text-sm flex items-center justify-center gap-2 text-gray-500 px-4 py-2 rounded-md" do %> - <%= lucide_icon("plus", class: "w-4 h-4") %> New entry - <% end %>
diff --git a/app/views/valuations/_form_row.html.erb b/app/views/valuations/_form_row.html.erb index 8c0ddb9a..3301c6cd 100644 --- a/app/views/valuations/_form_row.html.erb +++ b/app/views/valuations/_form_row.html.erb @@ -1,15 +1,16 @@ -<%# - Locals: - - f: form object for valuation - - form_icon: string representing the icon to be displayed - - submit_button_text: string representing the text on the submit button -%> -
-
- <%= lucide_icon(form_icon, class: "w-4 h-4 text-gray-500") %> +<%# locals (f:, form_icon:, submit_button_text:)%> +
+
+
+ <%= lucide_icon(form_icon, class: "w-4 h-4 text-gray-500") %> +
+
+
+ <%= f.date_field :date, class: "border border-alpha-black-200 bg-white rounded-lg shadow-xs min-w-[200px] px-3 py-1.5 text-gray-900 text-sm" %> + <%= f.number_field :value, step: :any, placeholder: number_to_currency(0), in: 0.00..100000000.00, required: 'required', class: "bg-white border border-alpha-black-200 rounded-lg shadow-xs text-gray-900 text-sm px-3 py-1.5 text-right" %> +
+
+ <%= link_to "Cancel", account_path(@valuation.account), class: "text-sm text-gray-900 hover:text-gray-800 font-medium px-3 py-1.5" %> + <%= f.submit submit_button_text, class: "bg-gray-50 rounded-lg font-medium px-3 py-1.5 cursor-pointer hover:bg-gray-100 text-sm" %>
- <%= f.date_field :date, class: "border border-alpha-black-200 bg-white rounded-lg shadow-xs min-w-[200px] px-3 py-1.5" %> - <%= f.number_field :value, step: :any, placeholder: number_to_currency(0), in: 0.00..100000000.00, required: 'required', class: "bg-white border border-alpha-black-200 rounded-lg shadow-xs px-3 py-1.5" %> - <%= link_to "Cancel", account_path(@valuation.account), class: "text-sm text-gray-900 hover:text-gray-800 font-medium px-3 py-1.5" %> - <%= f.submit submit_button_text, class: "bg-gray-50 rounded-lg font-medium px-3 py-1.5 cursor-pointer hover:bg-gray-100 text-sm" %>
diff --git a/app/views/valuations/create.turbo_stream.erb b/app/views/valuations/create.turbo_stream.erb index aeae48ed..03a83652 100644 --- a/app/views/valuations/create.turbo_stream.erb +++ b/app/views/valuations/create.turbo_stream.erb @@ -1,3 +1,4 @@ -<%# TODO: We need a way to determine the order the new valuation needs to be in the array, calculate the trend, and append it to the right spot %> <%= turbo_stream.update Valuation.new, "" %> <%= turbo_stream.append "notification-tray", partial: "shared/notification", locals: { type: "success", content: "Valuation created" } %> +<%= turbo_stream.replace "valuations_list", partial: "accounts/account_valuation_list", locals: { valuation_series: @account.valuation_series } %> +<%= turbo_stream.replace "sync_message", partial: "accounts/sync_message", locals: { is_syncing: true } %> diff --git a/app/views/valuations/destroy.turbo_stream.erb b/app/views/valuations/destroy.turbo_stream.erb index fad56f55..8153433b 100644 --- a/app/views/valuations/destroy.turbo_stream.erb +++ b/app/views/valuations/destroy.turbo_stream.erb @@ -1,2 +1,4 @@ <%= turbo_stream.remove @valuation %> <%= turbo_stream.append "notification-tray", partial: "shared/notification", locals: { type: "success", content: "Valuation deleted" } %> +<%= turbo_stream.replace "valuations_list", partial: "accounts/account_valuation_list", locals: { valuation_series: @account.valuation_series } %> +<%= turbo_stream.replace "sync_message", partial: "accounts/sync_message", locals: { is_syncing: true } %> diff --git a/app/views/valuations/edit.html.erb b/app/views/valuations/edit.html.erb index 0c9f5bd0..3bd87775 100644 --- a/app/views/valuations/edit.html.erb +++ b/app/views/valuations/edit.html.erb @@ -1,7 +1,7 @@
-

Edit Valuation: <%= @valuation.type %>

+

Edit Valuation

<%= turbo_frame_tag dom_id(@valuation) do %> - <%= form_with model: @valuation, url: valuation_path(@valuation), scope: :valuation do |f| %> + <%= form_with model: @valuation, url: valuation_path(@valuation), html: { class: "" } do |f| %> <%= render 'form_row', f: f, form_icon: "pencil-line", submit_button_text: "Update" %> <% end %> <% end %> diff --git a/app/views/valuations/new.html.erb b/app/views/valuations/new.html.erb index 7ceeb4b8..62c2239a 100644 --- a/app/views/valuations/new.html.erb +++ b/app/views/valuations/new.html.erb @@ -1,8 +1,9 @@

Add Valuation: <%= @account.name %>

<%= turbo_frame_tag dom_id(Valuation.new) do %> - <%= form_with model: [@account, @valuation], url: account_valuations_path(@account), scope: :valuation do |f| %> + <%= form_with model: [@account, @valuation], url: account_valuations_path(@account), html: { class: "" } do |f| %> <%= render 'form_row', f: f, form_icon: "plus", submit_button_text: "Add" %> <% end %> +
<% end %>
diff --git a/db/migrate/20240221004818_remove_valuation_type.rb b/db/migrate/20240221004818_remove_valuation_type.rb new file mode 100644 index 00000000..fa7d59be --- /dev/null +++ b/db/migrate/20240221004818_remove_valuation_type.rb @@ -0,0 +1,5 @@ +class RemoveValuationType < ActiveRecord::Migration[7.2] + def change + remove_column :valuations, :type, :string + end +end diff --git a/db/migrate/20240222144849_add_status_to_account.rb b/db/migrate/20240222144849_add_status_to_account.rb new file mode 100644 index 00000000..d35c242c --- /dev/null +++ b/db/migrate/20240222144849_add_status_to_account.rb @@ -0,0 +1,5 @@ +class AddStatusToAccount < ActiveRecord::Migration[7.2] + def change + add_column :accounts, :status, :string, default: "OK" + end +end diff --git a/db/schema.rb b/db/schema.rb index 7479f19b..f9c3f52d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_02_15_201527) do +ActiveRecord::Schema[7.2].define(version: 2024_02_22_144849) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -78,6 +78,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_02_15_201527) do t.string "original_currency", default: "USD" t.decimal "converted_balance", precision: 19, scale: 4, default: "0.0" t.string "converted_currency", default: "USD" + t.string "status", default: "OK" t.index ["accountable_type"], name: "index_accounts_on_accountable_type" t.index ["family_id"], name: "index_accounts_on_family_id" end @@ -207,7 +208,6 @@ ActiveRecord::Schema[7.2].define(version: 2024_02_15_201527) do end create_table "valuations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "type", null: false t.uuid "account_id", null: false t.date "date", null: false t.decimal "value", precision: 19, scale: 4, null: false diff --git a/db/seeds.rb b/db/seeds.rb index 23187d3d..de405863 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -32,16 +32,15 @@ account = Account.create_or_find_by(name: "Seed Property Account", accountable: puts "Account created: #{account.name}" # Represent user-defined "Valuations" at various dates -appraisals = [ - { date: Date.today - 30, balance: 300000 }, - { date: Date.today - 22, balance: 300700 }, - { date: Date.today - 17, balance: 301400 }, - { date: Date.today - 10, balance: 300000 }, - { date: Date.today - 3, balance: 301900 } +valuations = [ + { date: Date.today - 30, value: 300000 }, + { date: Date.today - 22, value: 300700 }, + { date: Date.today - 17, value: 301400 }, + { date: Date.today - 10, value: 300000 }, + { date: Date.today - 3, value: 301900 } ] -# In prod, this would be calculated from the current balance and the appraisals with a background job -# Hardcoded for readability +# Represent system-generated "Balances" at various dates, based on valuations balances = [ { date: Date.today - 30, balance: 300000 }, { date: Date.today - 29, balance: 300000 }, @@ -73,17 +72,16 @@ balances = [ { date: Date.today - 3, balance: 301900 }, { date: Date.today - 2, balance: 301900 }, { date: Date.today - 1, balance: 301900 }, - { date: Date.today, balance: 302000 } + { date: Date.today, balance: current_balance } ] - -appraisals.each do |appraisal| - Appraisal.find_or_create_by( +valuations.each do |valuation| + Valuation.find_or_create_by( account_id: account.id, - date: appraisal[:date] - ) do |appraisal_record| - appraisal_record.value = appraisal[:balance] - appraisal_record.currency = "USD" + date: valuation[:date] + ) do |valuation_record| + valuation_record.value = valuation[:value] + valuation_record.currency = "USD" end end @@ -93,6 +91,5 @@ balances.each do |balance| date: balance[:date] ) do |balance_record| balance_record.balance = balance[:balance] - balance_record.currency = "USD" end end diff --git a/test/fixtures/valuations.yml b/test/fixtures/valuations.yml index e51bcdbd..1568ef5b 100644 --- a/test/fixtures/valuations.yml +++ b/test/fixtures/valuations.yml @@ -1,13 +1,11 @@ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: - type: "Appraisal" value: 9.99 date: 2024-02-15 account: dylan_checking two: - type: "Appraisal" value: 9.99 date: 2024-02-15 account: richards_savings diff --git a/test/jobs/account_balance_sync_job_test.rb b/test/jobs/account_balance_sync_job_test.rb new file mode 100644 index 00000000..28014d1f --- /dev/null +++ b/test/jobs/account_balance_sync_job_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AccountBalanceSyncJobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end