mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-06 22:15:20 +02:00
Extract reconciliation methods to concern
This commit is contained in:
parent
18271ce005
commit
d459ebdad8
4 changed files with 311 additions and 293 deletions
|
@ -1,8 +1,5 @@
|
|||
class Account < ApplicationRecord
|
||||
InvalidBalanceError = Class.new(StandardError)
|
||||
|
||||
include Syncable, Monetizable, Chartable, Linkable, Enrichable
|
||||
include AASM
|
||||
include AASM, Syncable, Chartable, Linkable, Enrichable, Reconcileable
|
||||
|
||||
validates :name, :balance, :currency, presence: true
|
||||
|
||||
|
@ -17,7 +14,7 @@ class Account < ApplicationRecord
|
|||
has_many :holdings, dependent: :destroy
|
||||
has_many :balances, dependent: :destroy
|
||||
|
||||
monetize :balance, :cash_balance, :non_cash_balance
|
||||
|
||||
|
||||
enum :classification, { asset: "asset", liability: "liability" }, validate: { allow_nil: true }
|
||||
|
||||
|
@ -137,86 +134,6 @@ class Account < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def update_current_balance(balance:, cash_balance:)
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
||||
|
||||
if opening_anchor_valuation.present? && valuations.where(kind: "recon").empty?
|
||||
adjust_opening_balance_with_delta(balance:, cash_balance:)
|
||||
else
|
||||
reconcile_balance!(balance:, cash_balance:, date: Date.current)
|
||||
end
|
||||
end
|
||||
|
||||
def reconcile_balance!(balance:, cash_balance:, date:)
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
||||
raise InvalidBalanceError, "Linked accounts cannot be reconciled" if linked?
|
||||
|
||||
existing_valuation = valuations.joins(:entry).where(kind: "recon", entry: { date: Date.current }).first
|
||||
|
||||
if existing_valuation.present?
|
||||
existing_valuation.update!(
|
||||
balance: balance,
|
||||
cash_balance: cash_balance
|
||||
)
|
||||
else
|
||||
entries.create!(
|
||||
date: date,
|
||||
name: Valuation::Name.new("recon", self.accountable_type),
|
||||
amount: balance,
|
||||
currency: self.currency,
|
||||
entryable: Valuation.new(
|
||||
kind: "recon",
|
||||
balance: balance,
|
||||
cash_balance: 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
|
||||
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
||||
|
||||
transaction do
|
||||
if opening_anchor_valuation
|
||||
opening_anchor_valuation.update!(
|
||||
balance: balance,
|
||||
cash_balance: cash_balance
|
||||
)
|
||||
|
||||
opening_anchor_valuation.entry.update!(amount: balance)
|
||||
opening_anchor_valuation.entry.update!(date: date) unless date.nil?
|
||||
|
||||
opening_anchor_valuation
|
||||
else
|
||||
entry = entries.create!(
|
||||
date: date || fallback_opening_date,
|
||||
name: Valuation::Name.new("opening_anchor", self.accountable_type),
|
||||
amount: balance,
|
||||
currency: self.currency,
|
||||
entryable: Valuation.new(
|
||||
kind: "opening_anchor",
|
||||
balance: balance,
|
||||
cash_balance: cash_balance,
|
||||
)
|
||||
)
|
||||
|
||||
entry.valuation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def start_date
|
||||
first_entry_date = entries.minimum(:date) || Date.current
|
||||
|
@ -245,20 +162,4 @@ class Account < ApplicationRecord
|
|||
def long_subtype_label
|
||||
accountable_class.long_subtype_label_for(subtype) || accountable_class.display_name
|
||||
end
|
||||
|
||||
# For depository accounts, this is 0 (total balance is liquid cash)
|
||||
# For all other accounts, this represents "asset value" or "debt value"
|
||||
# (i.e. Investment accounts would refer to this as "holdings value")
|
||||
def non_cash_balance
|
||||
balance - cash_balance
|
||||
end
|
||||
|
||||
private
|
||||
def opening_anchor_valuation
|
||||
valuations.opening_anchor.first
|
||||
end
|
||||
|
||||
def current_anchor_valuation
|
||||
valuations.current_anchor.first
|
||||
end
|
||||
end
|
||||
|
|
109
app/models/account/reconcileable.rb
Normal file
109
app/models/account/reconcileable.rb
Normal file
|
@ -0,0 +1,109 @@
|
|||
# Methods for updating the historical balances of an account (opening, current, and arbitrary date reconciliations)
|
||||
module Account::Reconcileable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include Monetizable
|
||||
|
||||
monetize :balance, :cash_balance, :non_cash_balance
|
||||
end
|
||||
|
||||
InvalidBalanceError = Class.new(StandardError)
|
||||
|
||||
# For depository accounts, this is 0 (total balance is liquid cash)
|
||||
# For all other accounts, this represents "asset value" or "debt value"
|
||||
# (i.e. Investment accounts would refer to this as "holdings value")
|
||||
def non_cash_balance
|
||||
balance - cash_balance
|
||||
end
|
||||
|
||||
def reconcile_balance!(balance:, cash_balance:, date:)
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
||||
raise InvalidBalanceError, "Linked accounts cannot be reconciled" if linked?
|
||||
|
||||
existing_valuation = valuations.joins(:entry).where(kind: "recon", entry: { date: Date.current }).first
|
||||
|
||||
if existing_valuation.present?
|
||||
existing_valuation.update!(
|
||||
balance: balance,
|
||||
cash_balance: cash_balance
|
||||
)
|
||||
else
|
||||
entries.create!(
|
||||
date: date,
|
||||
name: Valuation::Name.new("recon", self.accountable_type),
|
||||
amount: balance,
|
||||
currency: self.currency,
|
||||
entryable: Valuation.new(
|
||||
kind: "recon",
|
||||
balance: balance,
|
||||
cash_balance: cash_balance
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def update_current_balance(balance:, cash_balance:)
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
||||
|
||||
if opening_anchor_valuation.present? && valuations.where(kind: "recon").empty?
|
||||
adjust_opening_balance_with_delta(balance:, cash_balance:)
|
||||
else
|
||||
reconcile_balance!(balance:, cash_balance:, date: Date.current)
|
||||
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
|
||||
|
||||
raise InvalidBalanceError, "Cash balance cannot exceed balance" if cash_balance > balance
|
||||
|
||||
transaction do
|
||||
if opening_anchor_valuation
|
||||
opening_anchor_valuation.update!(
|
||||
balance: balance,
|
||||
cash_balance: cash_balance
|
||||
)
|
||||
|
||||
opening_anchor_valuation.entry.update!(amount: balance)
|
||||
opening_anchor_valuation.entry.update!(date: date) unless date.nil?
|
||||
|
||||
opening_anchor_valuation
|
||||
else
|
||||
entry = entries.create!(
|
||||
date: date || fallback_opening_date,
|
||||
name: Valuation::Name.new("opening_anchor", self.accountable_type),
|
||||
amount: balance,
|
||||
currency: self.currency,
|
||||
entryable: Valuation.new(
|
||||
kind: "opening_anchor",
|
||||
balance: balance,
|
||||
cash_balance: cash_balance,
|
||||
)
|
||||
)
|
||||
|
||||
entry.valuation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def opening_anchor_valuation
|
||||
valuations.opening_anchor.first
|
||||
end
|
||||
|
||||
def current_anchor_valuation
|
||||
valuations.current_anchor.first
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue