mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-07 14:35:23 +02:00
Migrate valuations controller to new reconciliation methods
This commit is contained in:
parent
25f0c78c47
commit
d80cb9f812
19 changed files with 187 additions and 171 deletions
|
@ -14,8 +14,6 @@ class Account < ApplicationRecord
|
|||
has_many :holdings, dependent: :destroy
|
||||
has_many :balances, dependent: :destroy
|
||||
|
||||
|
||||
|
||||
enum :classification, { asset: "asset", liability: "liability" }, validate: { allow_nil: true }
|
||||
|
||||
scope :visible, -> { where(status: [ "draft", "active" ]) }
|
||||
|
@ -120,11 +118,6 @@ class Account < ApplicationRecord
|
|||
.order(amount: :desc)
|
||||
end
|
||||
|
||||
|
||||
def update_balance(balance:, date: Date.current, currency: nil, notes: nil)
|
||||
Account::BalanceUpdater.new(self, balance:, currency:, date:, notes:).update
|
||||
end
|
||||
|
||||
def update_currency!(new_currency)
|
||||
raise "Currency cannot be changed" if linked?
|
||||
|
||||
|
@ -134,7 +127,6 @@ class Account < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def start_date
|
||||
first_entry_date = entries.minimum(:date) || Date.current
|
||||
first_entry_date - 1.day
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
class Account::BalanceUpdater
|
||||
def initialize(account, balance:, currency: nil, date: Date.current, notes: nil)
|
||||
@account = account
|
||||
@balance = balance.to_d
|
||||
@currency = currency
|
||||
@date = date.to_date
|
||||
@notes = notes
|
||||
end
|
||||
|
||||
def update
|
||||
return Result.new(success?: true, updated?: false) unless requires_update?
|
||||
|
||||
Account.transaction do
|
||||
if date == Date.current
|
||||
account.balance = balance
|
||||
account.currency = currency if currency.present?
|
||||
account.save!
|
||||
end
|
||||
|
||||
valuation_entry = account.entries.valuations.find_or_initialize_by(date: date) do |entry|
|
||||
entry.entryable = Valuation.new(
|
||||
kind: "recon",
|
||||
balance: balance,
|
||||
cash_balance: balance
|
||||
)
|
||||
end
|
||||
|
||||
valuation_entry.amount = balance
|
||||
valuation_entry.currency = currency if currency.present?
|
||||
valuation_entry.name = valuation_name(valuation_entry, account)
|
||||
valuation_entry.notes = notes if notes.present?
|
||||
valuation_entry.save!
|
||||
end
|
||||
|
||||
account.sync_later
|
||||
|
||||
Result.new(success?: true, updated?: true)
|
||||
rescue => e
|
||||
message = Rails.env.development? ? e.message : "Unable to update account values. Please try again."
|
||||
Result.new(success?: false, updated?: false, error_message: message)
|
||||
end
|
||||
|
||||
private
|
||||
attr_reader :account, :balance, :currency, :date, :notes
|
||||
|
||||
Result = Struct.new(:success?, :updated?, :error_message)
|
||||
|
||||
def requires_update?
|
||||
date != Date.current || account.balance != balance || account.currency != currency
|
||||
end
|
||||
|
||||
def valuation_name(valuation_entry, account)
|
||||
Valuation::Name.new(valuation_entry.entryable.kind, account.accountable_type).to_s
|
||||
end
|
||||
end
|
|
@ -42,6 +42,7 @@ class Account::OverviewForm
|
|||
# Update name if provided
|
||||
if name.present? && name != account.name
|
||||
account.update!(name: name)
|
||||
account.lock_attr!(:name)
|
||||
updated = true
|
||||
end
|
||||
|
||||
|
|
|
@ -29,18 +29,26 @@ module Account::Reconcileable
|
|||
@opening_date ||= opening_anchor_valuation&.entry&.date
|
||||
end
|
||||
|
||||
def reconcile_balance!(balance:, cash_balance:, date:)
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
||||
def reconcile_balance!(balance:, cash_balance: nil, date: nil)
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance.present? && cash_balance > balance
|
||||
raise InvalidBalanceError, "Linked accounts cannot be reconciled" if linked?
|
||||
|
||||
derived_cash_balance = cash_balance.present? ? cash_balance : choose_cash_balance_from_balance(balance)
|
||||
|
||||
if date.nil?
|
||||
update_current_balance!(balance:, cash_balance: derived_cash_balance)
|
||||
return
|
||||
end
|
||||
|
||||
existing_valuation = valuations.joins(:entry).where(kind: "recon", entry: { date: date }).first
|
||||
|
||||
transaction do
|
||||
if existing_valuation.present?
|
||||
existing_valuation.update!(
|
||||
balance: balance,
|
||||
cash_balance: cash_balance
|
||||
cash_balance: derived_cash_balance
|
||||
)
|
||||
existing_valuation.entry.update!(amount: balance)
|
||||
else
|
||||
entries.create!(
|
||||
date: date,
|
||||
|
@ -50,43 +58,36 @@ module Account::Reconcileable
|
|||
entryable: Valuation.new(
|
||||
kind: "recon",
|
||||
balance: balance,
|
||||
cash_balance: cash_balance
|
||||
cash_balance: derived_cash_balance
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
# Update cached balance fields on account when reconciling for current date
|
||||
if date == Date.current
|
||||
update!(balance: balance, cash_balance: cash_balance)
|
||||
update!(balance: balance, cash_balance: derived_cash_balance)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_current_balance!(balance:, cash_balance:)
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
||||
def update_current_balance!(balance:, cash_balance: nil)
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance.present? && cash_balance > balance
|
||||
|
||||
derived_cash_balance = cash_balance.present? ? cash_balance : choose_cash_balance_from_balance(balance)
|
||||
|
||||
transaction do
|
||||
if opening_anchor_valuation.present? && valuations.where(kind: "recon").empty?
|
||||
adjust_opening_balance_with_delta(balance:, cash_balance:)
|
||||
# See test for explanation - Depository accounts are handled as a special case for current balance updates
|
||||
if opening_anchor_valuation.present? && valuations.where(kind: "recon").empty? && self.depository?
|
||||
adjust_opening_balance_with_delta!(balance:, cash_balance: derived_cash_balance)
|
||||
else
|
||||
reconcile_balance!(balance:, cash_balance:, date: Date.current)
|
||||
reconcile_balance!(balance:, cash_balance: derived_cash_balance, date: Date.current)
|
||||
end
|
||||
|
||||
# Always update cached balance fields when updating current balance
|
||||
update!(balance: balance, cash_balance: cash_balance)
|
||||
update!(balance: balance, cash_balance: derived_cash_balance)
|
||||
end
|
||||
end
|
||||
|
||||
def adjust_opening_balance_with_delta(balance:, cash_balance:)
|
||||
delta = self.balance - balance
|
||||
cash_delta = self.cash_balance - cash_balance
|
||||
|
||||
set_or_update_opening_balance!(
|
||||
balance: balance - delta,
|
||||
cash_balance: cash_balance - cash_delta
|
||||
)
|
||||
end
|
||||
|
||||
def set_or_update_opening_balance!(balance:, cash_balance:, date: nil)
|
||||
# A reasonable start date for most accounts to fill up adequate history for graphs
|
||||
fallback_opening_date = 2.years.ago.to_date
|
||||
|
@ -130,4 +131,24 @@ module Account::Reconcileable
|
|||
def current_anchor_valuation
|
||||
valuations.current_anchor.first
|
||||
end
|
||||
|
||||
def adjust_opening_balance_with_delta!(balance:, cash_balance:)
|
||||
delta = self.balance - balance
|
||||
cash_delta = self.cash_balance - cash_balance
|
||||
|
||||
set_or_update_opening_balance!(
|
||||
balance: balance - delta,
|
||||
cash_balance: cash_balance - cash_delta
|
||||
)
|
||||
end
|
||||
|
||||
# For depository accounts, the cash balance is the same as the balance always
|
||||
# Otherwise, if not specified, we assume cash balance is 0
|
||||
def choose_cash_balance_from_balance(balance)
|
||||
if self.depository?
|
||||
balance
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue