mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-06 05:55:21 +02:00
Add reconciliation manager (#2459)
* Add reconciliation manager * Fix notes editing
This commit is contained in:
parent
89cc64418e
commit
52333e3fa6
11 changed files with 273 additions and 64 deletions
16
app/models/account/reconcileable.rb
Normal file
16
app/models/account/reconcileable.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
module Account::Reconcileable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def create_reconciliation(balance:, date:, dry_run: false)
|
||||
reconciliation_manager.reconcile_balance(balance: balance, date: date, dry_run: dry_run)
|
||||
end
|
||||
|
||||
def update_reconciliation(existing_valuation_entry, balance:, date:, dry_run: false)
|
||||
reconciliation_manager.reconcile_balance(balance: balance, date: date, existing_valuation_entry: existing_valuation_entry, dry_run: dry_run)
|
||||
end
|
||||
|
||||
private
|
||||
def reconciliation_manager
|
||||
@reconciliation_manager ||= Account::ReconciliationManager.new(self)
|
||||
end
|
||||
end
|
90
app/models/account/reconciliation_manager.rb
Normal file
90
app/models/account/reconciliation_manager.rb
Normal file
|
@ -0,0 +1,90 @@
|
|||
class Account::ReconciliationManager
|
||||
attr_reader :account
|
||||
|
||||
def initialize(account)
|
||||
@account = account
|
||||
end
|
||||
|
||||
# Reconciles balance by creating a Valuation entry. If existing valuation is provided, it will be updated instead of creating a new one.
|
||||
def reconcile_balance(balance:, date: Date.current, dry_run: false, existing_valuation_entry: nil)
|
||||
old_balance_components = old_balance_components(reconciliation_date: date, existing_valuation_entry: existing_valuation_entry)
|
||||
prepared_valuation = prepare_reconciliation(balance, date, existing_valuation_entry)
|
||||
|
||||
unless dry_run
|
||||
prepared_valuation.save!
|
||||
account.sync_later
|
||||
end
|
||||
|
||||
ReconciliationResult.new(
|
||||
success?: true,
|
||||
old_cash_balance: old_balance_components[:cash_balance],
|
||||
old_balance: old_balance_components[:balance],
|
||||
new_cash_balance: derived_cash_balance(date: date, total_balance: prepared_valuation.amount),
|
||||
new_balance: prepared_valuation.amount,
|
||||
error_message: nil
|
||||
)
|
||||
rescue => e
|
||||
ReconciliationResult.new(
|
||||
success?: false,
|
||||
error_message: e.message
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
# Returns before -> after OR error message
|
||||
ReconciliationResult = Struct.new(
|
||||
:success?,
|
||||
:old_cash_balance,
|
||||
:old_balance,
|
||||
:new_cash_balance,
|
||||
:new_balance,
|
||||
:error_message,
|
||||
keyword_init: true
|
||||
)
|
||||
|
||||
def prepare_reconciliation(balance, date, existing_valuation)
|
||||
valuation_record = existing_valuation ||
|
||||
account.entries.valuations.find_by(date: date) || # In case of conflict, where existing valuation is not passed as arg, but one exists
|
||||
account.entries.build(
|
||||
name: Valuation.build_reconciliation_name(account.accountable_type),
|
||||
entryable: Valuation.new(kind: "reconciliation")
|
||||
)
|
||||
|
||||
valuation_record.assign_attributes(
|
||||
date: date,
|
||||
amount: balance,
|
||||
currency: account.currency
|
||||
)
|
||||
|
||||
valuation_record
|
||||
end
|
||||
|
||||
def derived_cash_balance(date:, total_balance:)
|
||||
balance_components_for_reconciliation_date = get_balance_components_for_date(date)
|
||||
|
||||
return nil unless balance_components_for_reconciliation_date[:balance] && balance_components_for_reconciliation_date[:cash_balance]
|
||||
|
||||
# We calculate the existing non-cash balance, which for investments would represents "holdings" for the date of reconciliation
|
||||
# Since the user is setting "total balance", we have to subtract the existing non-cash balance from the total balance to get the new cash balance
|
||||
existing_non_cash_balance = balance_components_for_reconciliation_date[:balance] - balance_components_for_reconciliation_date[:cash_balance]
|
||||
|
||||
total_balance - existing_non_cash_balance
|
||||
end
|
||||
|
||||
def old_balance_components(reconciliation_date:, existing_valuation_entry: nil)
|
||||
if existing_valuation_entry
|
||||
get_balance_components_for_date(existing_valuation_entry.date)
|
||||
else
|
||||
get_balance_components_for_date(reconciliation_date)
|
||||
end
|
||||
end
|
||||
|
||||
def get_balance_components_for_date(date)
|
||||
balance_record = account.balances.find_by(date: date, currency: account.currency)
|
||||
|
||||
{
|
||||
cash_balance: balance_record&.cash_balance,
|
||||
balance: balance_record&.balance
|
||||
}
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue